Skip to content

Commit 8a90ca3

Browse files
committed
docs(ses): document Compartment availability and OOM limits (#2742)
Adds a Limitations section to docs/lockdown.md describing three classes of threat that a Compartment cannot mitigate, all intrinsic to running multiple compartments inside a single JavaScript agent. Availability: every compartment shares the agent's single thread, so a guest that enters a non-terminating synchronous computation (an infinite loop, runaway recursion, or any other unresponsive synchronous call, including via a property accessor, proxy trap, or synchronously-resolved `then` callback) denies progress to every sibling and to the start compartment until the engine intervenes. Host code that wants to remain available must interact with guest objects across an asynchronous boundary, or run the guest in a separate worker or process where it can be terminated. Memory exhaustion: the ECMAScript specification does not require an engine to abort the agent on OOM (see the TC39 OOM-fails-fast proposal), so a malicious compartment can intentionally exhaust memory and surface a `RangeError` or other allocation failure inside an unrelated synchronous call, including one into an object exported from another compartment. The victim is then left in a partially updated state, turning the availability concern into an integrity concern on otherwise trusted exports. Timing side-channels: the default Compartment deliberately excludes `Date`, `performance.now`, `setTimeout`, `setImmediate`, and every other host scheduling primitive, but any asynchronous boundary the guest can drive (another worker, another process, a network peer, a host-supplied promise resolved on a host-driven schedule) recovers wall-clock duration from the round-trip. Defeating timing side-channels therefore requires auditing every reachable async boundary, not just enumerating timer-like APIs; the host's mitigation menu includes running the suspect guest in a separate worker or process, throttling outbound message rate, and padding guest-visible message delivery so the round-trip distribution conveys less about host-internal events. For the broader threat catalog, the section cites Agoric's Taxonomy of Security Issues paper. The new section is cross-linked from packages/ses/README.md's Compartment section so readers reach it from the place Compartment is introduced. Fixes #2742
1 parent 814dfa1 commit 8a90ca3

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

docs/lockdown.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,3 +1218,92 @@ The "`__`" in the option name indicates that this option is temporary. XS now
12181218
has a fast native `harden`, but SwingSet currently runs on node/v8, which does
12191219
not. If node/v8 ever implements a fast native `harden`, we hope to deprecate
12201220
and eventually remove this option.
1221+
1222+
# Limitations
1223+
1224+
Compartments isolate the *authority* and *intrinsics* available to guest code,
1225+
but every compartment in a realm shares the same JavaScript *agent*.
1226+
In engine terms, that is a single thread of execution and a single heap.
1227+
The following limitations are intrinsic to running JavaScript in one agent and
1228+
are not threats a `Compartment` can mitigate.
1229+
A host that needs to defend against them must impose a coarser boundary,
1230+
as a separate worker or process around the
1231+
suspect code.
1232+
1233+
## Availability
1234+
1235+
A compartment cannot protect availability for code running in sibling
1236+
compartments or in the start compartment.
1237+
Because all compartments share the agent's single thread, a guest that enters
1238+
an infinite loop (such as `for (;;) {}`), a runaway recursion, or any other
1239+
non-terminating synchronous computation denies synchronous progress to every
1240+
other compartment until the engine itself intervenes (for example, by hitting
1241+
an engine-imposed call-stack limit or by the host terminating the worker or
1242+
process).
1243+
1244+
This applies even to ostensibly cooperative code.
1245+
A guest method invoked synchronously by host code can refuse to return.
1246+
This includes calls through a property accessor, a proxy trap, a `then`
1247+
callback resolved synchronously, or any other synchronous call.
1248+
Host code that wishes to remain available in the face of an unresponsive guest
1249+
must arrange to interact with guest objects across an asynchronous boundary
1250+
(for example, by using promises and a watchdog timer in a separate agent), or
1251+
must run the guest in a separate worker or process where the host can
1252+
terminate it.
1253+
1254+
## Memory exhaustion
1255+
1256+
A compartment cannot protect the integrity of its exports against an
1257+
out-of-memory (OOM) attack mounted by another compartment in the same agent.
1258+
The ECMAScript specification does not require an engine to abort the agent on
1259+
OOM; see the TC39
1260+
[OOM-fails-fast proposal](https://github.com/tc39/proposal-oom-fails-fast)
1261+
for the current state of efforts to standardize that behavior.
1262+
In its absence, a malicious compartment can intentionally allocate stack
1263+
frames or heap objects until the engine raises a `RangeError` or other
1264+
allocation failure at an arbitrary point in unrelated code.
1265+
1266+
The failure can surface inside an unrelated synchronous call, including a
1267+
call into an object exported from another compartment.
1268+
The victim object may then be left in a partially updated state.
1269+
The result is an *integrity* compromise (broken invariants on otherwise
1270+
trusted objects) on top of the *availability* compromise (the agent or
1271+
process may exit).
1272+
Hosts that need to defend exported objects against this class of attack
1273+
should run untrusted guests in a separate agent (such as a worker or
1274+
subprocess) with its own heap, and treat any synchronous call into a guest as
1275+
potentially failure-inducing.
1276+
1277+
## Timing side-channels
1278+
1279+
A compartment cannot prevent a guest from measuring durations.
1280+
The default `Compartment` deliberately excludes every standard timer:
1281+
`Date` is not in the global scope, `performance.now` is unavailable, and
1282+
neither `setTimeout`, `setImmediate`, nor any other host scheduling
1283+
primitive is exposed.
1284+
Removing the obvious clocks is necessary but not sufficient.
1285+
1286+
A guest with the ability to make asynchronous calls to any other agent
1287+
(another worker, another process, a network peer, or even a host-supplied
1288+
promise that resolves on a host-driven schedule) can recover wall-clock
1289+
duration from the round-trip.
1290+
Two consecutive round-trips to the same correspondent yield a coarse
1291+
interval, and many such samples yield a usable timer at the resolution of
1292+
the round-trip jitter.
1293+
Any asynchronous boundary that the guest can drive at will is therefore a
1294+
potential timing source, regardless of whether the host intended it as one.
1295+
1296+
Confined code that needs to defeat timing side-channels requires a careful
1297+
audit of every async boundary the guest can reach, not just an enumeration
1298+
of timer-like APIs in the global scope.
1299+
The mitigations available to a host include running the suspect guest in a
1300+
separate worker or process so that the timing channel does not leak into
1301+
the host's own heap, throttling the rate at which the guest can send
1302+
outbound messages, and padding the timing of guest-visible message delivery
1303+
so that the round-trip distribution conveys less information about
1304+
host-internal events.
1305+
1306+
For a fuller threat catalog of side-channels and other JavaScript-confinement
1307+
risks, see Agoric's
1308+
[Taxonomy of Security Issues](https://papers.agoric.com/taxonomy-of-security-issues/),
1309+
which surveys the broader space this section samples.

packages/ses/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,10 @@ const powerfulCompartment = new Compartment({
224224
powerfulCompartment.globalThis.Date = Date;
225225
```
226226

227+
Because every compartment shares one JavaScript agent, see
228+
[Limitations](../../docs/lockdown.md#limitations) for the availability and
229+
memory-exhaustion threats a `Compartment` cannot mitigate.
230+
227231
### Compartment + Lockdown
228232

229233
Together, Compartment and lockdown isolate client code in an environment with

0 commit comments

Comments
 (0)