Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions docs/lockdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -1218,3 +1218,92 @@ The "`__`" in the option name indicates that this option is temporary. XS now
has a fast native `harden`, but SwingSet currently runs on node/v8, which does
not. If node/v8 ever implements a fast native `harden`, we hope to deprecate
and eventually remove this option.

# Limitations

Compartments isolate the *authority* and *intrinsics* available to guest code,
but every compartment in a realm shares the same JavaScript *agent*.
In engine terms, that is a single thread of execution and a single heap.
The following limitations are intrinsic to running JavaScript in one agent and
are not threats a `Compartment` can mitigate.
A host that needs to defend against them must impose a coarser boundary,
as a separate worker or process around the
suspect code.

## Availability

A compartment cannot protect availability for code running in sibling
compartments or in the start compartment.
Because all compartments share the agent's single thread, a guest that enters
an infinite loop (such as `for (;;) {}`), a runaway recursion, or any other
non-terminating synchronous computation denies synchronous progress to every
other compartment until the engine itself intervenes (for example, by hitting
an engine-imposed call-stack limit or by the host terminating the worker or
process).

This applies even to ostensibly cooperative code.
A guest method invoked synchronously by host code can refuse to return.
This includes calls through a property accessor, a proxy trap, a `then`
callback resolved synchronously, or any other synchronous call.
Host code that wishes to remain available in the face of an unresponsive guest
must arrange to interact with guest objects across an asynchronous boundary
(for example, by using promises and a watchdog timer in a separate agent), or
must run the guest in a separate worker or process where the host can
terminate it.

## Memory exhaustion

A compartment cannot protect the integrity of its exports against an
out-of-memory (OOM) attack mounted by another compartment in the same agent.
The ECMAScript specification does not require an engine to abort the agent on
OOM; see the TC39
[OOM-fails-fast proposal](https://github.com/tc39/proposal-oom-fails-fast)
for the current state of efforts to standardize that behavior.
In its absence, a malicious compartment can intentionally allocate stack
frames or heap objects until the engine raises a `RangeError` or other
allocation failure at an arbitrary point in unrelated code.

The failure can surface inside an unrelated synchronous call, including a
call into an object exported from another compartment.
The victim object may then be left in a partially updated state.
The result is an *integrity* compromise (broken invariants on otherwise
trusted objects) on top of the *availability* compromise (the agent or
process may exit).
Hosts that need to defend exported objects against this class of attack
should run untrusted guests in a separate agent (such as a worker or
subprocess) with its own heap, and treat any synchronous call into a guest as
potentially failure-inducing.

## Timing side-channels

A compartment cannot prevent a guest from measuring durations.
The default `Compartment` deliberately excludes every standard timer:
`Date` is not in the global scope, `performance.now` is unavailable, and
neither `setTimeout`, `setImmediate`, nor any other host scheduling
primitive is exposed.
Removing the obvious clocks is necessary but not sufficient.

A guest with the ability to make asynchronous calls to any other agent
(another worker, another process, a network peer, or even a host-supplied
promise that resolves on a host-driven schedule) can recover wall-clock
duration from the round-trip.
Two consecutive round-trips to the same correspondent yield a coarse
interval, and many such samples yield a usable timer at the resolution of
the round-trip jitter.
Any asynchronous boundary that the guest can drive at will is therefore a
potential timing source, regardless of whether the host intended it as one.

Confined code that needs to defeat timing side-channels requires a careful
audit of every async boundary the guest can reach, not just an enumeration
of timer-like APIs in the global scope.
The mitigations available to a host include running the suspect guest in a
separate worker or process so that the timing channel does not leak into
the host's own heap, throttling the rate at which the guest can send
outbound messages, and padding the timing of guest-visible message delivery
so that the round-trip distribution conveys less information about
host-internal events.

For a fuller threat catalog of side-channels and other JavaScript-confinement
risks, see Agoric's
[Taxonomy of Security Issues](https://papers.agoric.com/taxonomy-of-security-issues/),
which surveys the broader space this section samples.
4 changes: 4 additions & 0 deletions packages/ses/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ const powerfulCompartment = new Compartment({
powerfulCompartment.globalThis.Date = Date;
```

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

### Compartment + Lockdown

Together, Compartment and lockdown isolate client code in an environment with
Expand Down
Loading