diff --git a/docs/lockdown.md b/docs/lockdown.md index 1a76278d9f..d3c851cfe7 100644 --- a/docs/lockdown.md +++ b/docs/lockdown.md @@ -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. diff --git a/packages/ses/README.md b/packages/ses/README.md index a76779c6a1..0779c316d9 100644 --- a/packages/ses/README.md +++ b/packages/ses/README.md @@ -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