|
| 1 | +A graduated sequence of example shim code, to better understand the security risks of the AsyncContext proposal to tc39. |
| 2 | + |
| 3 | +- [0-sync-context-original.js](./0-sync-context-original.js) is a class style non-transposed `Map`-based implementation of synchronous fluid binding, based on Justin's [Slide 6](https://docs.google.com/presentation/d/1yw4d0ca6v2Z2Vmrnac9E9XJFlC872LDQ4GFR17QdRzk/edit#slide=id.g198251ee25f_2_6). |
| 4 | + |
| 5 | +- [1-sync-context-maker.js](./1-sync-context-maker.js) is an ***objects-as-closures*** style non-transposed `Map`-based implementation of synchronous fluid binding. It is equivalent to [0-sync-context-original.js](./0-sync-context-original.js) except written in objects-as-closure style rather than class style. The externally visible differences are |
| 6 | + * The `makeSyncContext` function rather than the `SyncContext` class. |
| 7 | + * The resulting need to call the maker as a function, rather than using `new`. |
| 8 | + * The absence of anything analogous to a `SyncContext.prototype` object. |
| 9 | + * And the methods being own properties of the instance rather than inherited properties. |
| 10 | + |
| 11 | + For all of these differences, the class-style is more representative of the API one would actually propose and shim. We shift to objects-as-closure style only so that the semantics can be more easily understood. |
| 12 | + |
| 13 | +- [2-sync-context-shallow.js](./2-sync-context-shallow.js) is an equivalent ***shallow*** binding implementation, that should be observationally equivalent without using top-level state. Thereby showing the original is equally safe. This is closest to the classic model of fluid scoping. But it doesn't generalize to asynchronous fluid bindings since `wrap` would be impossible to implement. Hence our overall focus on deep-binding implementations. |
| 14 | + |
| 15 | +- [3-sync-context-deep.js](./3-sync-context-deep.js) is an equivalent deep-binding non-transposed ***`WeakMap`-based*** implementation. Shifting to `WeakMap` enables us to transpose. |
| 16 | + |
| 17 | +- [4-sync-context-transpose.js](./4-sync-context-transpose.js) is an equivalent deep-binding ***transposed*** `WeakMap`-based implementation. By transposing, we can remove all mutable state rooted in the problematic global mutable variable, moving that mutable state into the `SyncContext` instances, regaining some of the safe look of [2-sync-context-shallow.js](2-sync-context-shallow.js). |
| 18 | + |
| 19 | +- [5-async-context-original.js](./5-async-context-original.js) is a deep-binding non-transposed `Map`-based implementation of ***asynchronous*** fluid binding, based on Justin's [Slide 11](https://docs.google.com/presentation/d/1yw4d0ca6v2Z2Vmrnac9E9XJFlC872LDQ4GFR17QdRzk/edit#slide=id.g18e6eaa50e1_0_192) and [Slide 13](https://docs.google.com/presentation/d/1yw4d0ca6v2Z2Vmrnac9E9XJFlC872LDQ4GFR17QdRzk/edit#slide=id.g191c1f7e99f_0_0). It is identical to [0-sync-context-original.js](./0-sync-context-original.js) but for the addition of a `wrap` function. |
| 20 | + |
| 21 | +- [6-async-context-transpose.js](./6-async-context-transpose.js) is an equivalent deep-binding ***transposed*** `WeakMap`-based implementation of ***asynchronous*** fluid binding. It is identical to [4-sync-context-transpose.js](./4-sync-context-transpose.js) but for the addition of that `wrap` function. `wrap` still manipulates an encapsulated top-level mutable variable, but each value bound to this varible is transitively immutable and powerless. |
| 22 | + |
| 23 | +- [test-attack](../../test/async-contexts/test-attack.js) demonstates an attack, in which Carol creates a separate Alice and Bob that are confined and isolated from each other. They can communicate *only* as mediated by Carol. Carol introduces them *only* by giving each a new no-argument closure of Carol's creation, each of which logs what it does: invoke a method of the counterparty with no arguments. The two test cases show that Carol has in fact enabled Alice to communicate a secret to Bob even though the log is the same, and therefore independent of the secret. This would not be possible in Hardened JavaScript without `wrap`. |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +Without `wrap`, [6-async-context-transpose.js](./6-async-context-transpose.js) would still be observationally equivalent to [2-sync-context-shallow.js](2-sync-context-shallow.js), and therefore obviously safe. The top-level state manupulatd by `wrap` is still encapsulated. We assume that `wrap` itself is not exposed, but rather only used to explain the needed changes to the semantics of the internal `then` function. The internal `then` function explains the behavior of both the primordial `then` method and the `await` syntax, so its ability to manipulate top-level state via `wrap` remains worrisome. |
| 28 | + |
| 29 | +But notice that we already live with a similar hazard that we've come to realize is safe: The internal `then` function (and therefore the primordial `then` and the `await` syntax) already manipulate other top-level state that is not otherwise reachable: The job queue. It is in fact weird that no capability to the job queue is required to schedule jobs on this mutable job queue. Ambient access to *the one top level mutable* job queue does have some downsides that would be absent if we did not allow this exception: |
| 30 | + |
| 31 | +Say a hostile subgraph is fully encapsulated in a revocable membrane. Once the membrane is revoked, the hostile subgraph can no longer cause any effects on the world outside itself. [An omniscient garbage collector could therefore collect it](https://www.youtube.com/watch?v=oBqeDYETXME&list=PLKr-mvz8uvUgybLg53lgXSeLOp4BiwvB2&index=5&t=1574s), knowing that this collection would be unobservable. In the absence of this job-queue exception, that would also be true for actual garbage collectors. However, by using the power of the internal `then` function, the disconnected subgraph can keep rescheduling itself, and so remain resident, continuing to use both space and time resources. Had access to the job queue been mediated by a capability, access to the actual capability would have been severed by the membrane, so actual collectors could then sweep up the garbage. This demonstrates that the ambient internal `then` weakens availability. |
| 32 | + |
| 33 | +But the ambient internal `then` does not seem to endanger integrity, nor ***overt*** confidentiality --- leakage of info over [overt channels](https://agoric.com/blog/all/taxonomy-of-security-issues/). The key seems to be that computation isolated from all the objects in turn T1 cannot overtly observe any of the turns spawned by turn T1. It is at least plausible that the `AsyncContext` proposal, being observationally equivalent to [6-async-context-transpose.js](./6-async-context-transpose.js) is equally safe. The only difference from the obviously-safe [2-sync-context-shallow.js](2-sync-context-shallow.js) is the extension of context from a spawned turn T1 to the turns it spawns. The state associated with an `AsyncContext` instance is unobservable to |
| 34 | + * anything that cannot run in the spawned turn (by the safety properties of the existing `then`), |
| 35 | + * anything without access to that `AsyncContext` instance (by the safety demonstrated by [2-sync-context-shallow.js](2-sync-context-shallow.js)). |
0 commit comments