-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Surface SES on globalThis #307
Conversation
096d712
to
777e58b
Compare
190af04
to
3c10a06
Compare
3c10a06
to
0e83a28
Compare
@@ -2,6 +2,9 @@ User-visible changes in SES: | |||
|
|||
## Next release | |||
|
|||
* Adds support for modules to Compartments. | |||
* SES no longer exports anything. `Compartment`, `ModuleStaticRecord`, | |||
`lockdown`, and `harden` are all introduced as properties of `globalThis`. | |||
* Repair `Function.apply` and `TypeError.message` (as well as `.message` on | |||
all the other Error types), to tolerate what the npm `es-abstract` module | |||
does. This allows `tape` (version 4.x) to be loaded in a locked-down SES |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
packages/ses/src/lockdown-shim.js
Outdated
*/ | ||
|
||
// Circumvent the override mistake. | ||
const detachedProperties = enablePropertyOverrides(intrinsics); | ||
|
||
// Finally register and optionally freeze all the intrinsics. This | ||
// must be the operation that modifies the intrinsics. | ||
harden(intrinsics, registerOnly); | ||
harden(detachedProperties, registerOnly); | ||
lockdownHarden(intrinsics, registerOnly); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this harden
(aka lockdownHarden
) function that takes a second parameter? I couldn't find it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whose source is at https://github.com/Agoric/SES-shim/blob/0e83a286dc01ee16d20434548bd8bac0faab30bc/packages/make-hardener/src/main.js#L67 which takes only one parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked. lockdownHarden
, which is just the local name for harden
that is usable before lockdown
finishes, does not accept a second argument. To me, this looks like the registerOnly
behavior does not work and (because tests pass) is not tested. I don’t know what it is meant to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is registerOnly
supposed to mean or do? What is it needed for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no indication anywhere in the code or documentation of what registerOnly
means or should do. I would be glad to erase it in this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please do.
For a small security kernel, I'd prefer to err on the side of removing things we don't know the purpose of. Sometimes, in review, I'll know and clarify.
lockdown, | ||
harden, | ||
Compartment, | ||
ModuleStaticRecord, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All shimmed pervasively global names must be added to the appropriate whitelists. I don't see lockdown
and ModuleStaticRecord
(or StaticModuleRecord
) in intrinsic-names.js
I do see ModuleStaticRecord
in intrinsic-globals.js
, but I don't see lockdown
there.
lockdown
missing from whitelist.js
as well.
We should have a test that fails on such omission, because it is an easy mistake to keep making.
TODO(markm): the whole intrinsic management system now consists of nine separate files with too much redundancy, e.g., between intrinsic-names.js
, intrinsic-globals.js
, and whitelist.js
. We should simplify, make it easier to maintain, and self-test consistency among what redundancy remains. Also, check-intrinsics.js
seems vacuous. Bad edit from a previously meaningful state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed TODO as #317
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the kind of thing that I worry most about. Thanks for catching this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding lockdown
to the whitelist caused tests to fail, so I’ll come back to it when we have a firm resolution as to whether it should preserve itself after lockdown
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. But make sure the state as of merging this PR is consistent with itself.
packages/ses/src/lockdown-shim.js
Outdated
@@ -35,6 +33,19 @@ function assert(condition, message) { | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not import this from assert.js
? It is currently identical, and it will make it easy to upgrade all occurrences to the new assert system when it is time to do that.
packages/ses/README.md
Outdated
Alters the surrounding execution environment such that no object intrinsically | ||
accessible, like the array or function prototypes (`[].prototype` or `(() => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've been using the term "undeniable". "undeniably accessible" isn't great. But "intrinsically accessible" has no clear meaning. Better phrase appreciated.
packages/ses/README.md
Outdated
accessible, like the array or function prototypes (`[].prototype` or `(() => | ||
0).prototype`, can be replaced or subverted to attack other code in the same |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not .prototype
. It is .__proto__
or Object.getPrototypeOf
or Reflect.getPrototypeOf
.
Pedantic note that shouldn't affect this README.md
:
In some thread elsewhere @caridy points out that these means of ascending the prototype chain are deniable in theory. But we all agreed they are not practically deniable. @ljharb pointed out that one undeniable, %ThrowTypeError%
is obtained only by accessing the getter property of a property descriptor. This again is deniable in theory. So we're left with "reachable by syntax combined with universal reflective operations, like accessing [[Prototype]]
or accessing a property getter". Fortunately, this README need not be that precise about undeniability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread is at tc39/how-we-work#84
packages/ses/README.md
Outdated
0).prototype`, can be replaced or subverted to attack other code in the same | ||
execution environment; and tames other intrinsically accessible utilities like | ||
regular expressions, random numbers, and clocks, to minimize opportunities for | ||
malicious programs to covertly infer the behavior of other programs running in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"infer or interfere"
packages/ses/README.md
Outdated
import 'ses'; | ||
|
||
import 'my-vetted-shim'; | ||
|
||
lockdown(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, that shows off the simple one-file use case well. I'm on the fence as to whether my-vetted-shim
should be imported before ses
(to visually suggest that it is unconfined), or after (to remind readers that nothing is actually confined until lockdown()
is called).
But a more realistic usage summary would involve confined modules too. With the current feature set, that means writing an install-ses
module (consisting entirely of import 'ses'; lockdown();
), and doing the other imports before/after it:
import 'my-vetted-shim';
import 'install-ses';
import 'confined-module';
other_confined_code();
Do we want to show this here/now, or will the upcoming module-import support provide a better way? I'm hoping we'll have some docs that explain how to use this in a way similar to how folks are used to writing/running programs today, rather than putting off the practicalities in favor of a future application-loader/manifest/"larger-scale" ecosystem that we're still trying to figure out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my-vetted-shim
is not confined; which is why it, along with any code run before lockdown()
, must be vetted. IOW, all code run before lockdown()
can do powerful dangerous things, creating unlimited damage to everything run afterward, including lockdown()
itself.
Running before import 'ses';
vs between it and lockdown()
makes only one difference: whether harden
,ModuleStaticRecord
,Compartment
, and lockdown
are available as globals.
I just looked at the shim source and confirmed that import 'ses';
by itself causes no effects other than making these available as properties on the global object and thereby as global variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe @warner is speaking to the point of an install-ses
module that looks like:
import 'ses';
lockdown();
Such that in his example, confined-module
is running after lockdown.
My intuition is that this is dangerously clever since the so-called confined module still has access to power that lockdown was unable to expunge from the start compartment.
Not sure whether we should highlight that usage pattern, as I am uncertain about how dangerous it is.
packages/ses/README.md
Outdated
|
||
lockdown(); | ||
|
||
console.log(Object.isFrozen([].prototype)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[].__proto__
packages/ses/README.md
Outdated
|
||
SES introduces the `harden` function. | ||
*After* calling `lockdown`, the `harden` function ensures that whatever object | ||
you give it is also transitively frozen out to the execution environments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
environment's
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"transitively frozen" always causes confusion, it that people mistake it as being about immutability. We must always be clear that it is only transitive across own property traversal, in order to hide API surface. You do mention "surface" here but without a bit more context it is not clear what you mean.
Your example below perfectly illustrates what is frozen by harden. Perhaps have use
close over a variable it mutates, so you can explain what is not suppressed by harden
?
Then link to wherever we have a more complete explanation of harden
including its consistency check against of the [[Prototype]]
properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have no explanation, not even inline. #320
packages/ses/README.md
Outdated
`globalThis` and wholly independent system of modules, but otherwise shares | ||
the same batch of intrinsics like `Array` with the surrounding compartment. | ||
The concept of a compartment implies the existence of a "start compartment", | ||
the initial execution argument. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you mean "argument"
packages/ses/README.md
Outdated
const c = new Compartment({ | ||
print: harden(console.log), | ||
}); | ||
|
||
c.evaluate(` | ||
print("Hello! Hello?"); | ||
print('Hello! Hello?'); | ||
`); | ||
``` | ||
|
||
The new compartment has a different global object than the start compartment. | ||
The global object is initially mutable. | ||
Locking down the start compartment hardened many of the intrinsics in global |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid the phrase "locking down the start compartment". This invites the confusion the rest of the paragraph corrects. lockdown
locks down the realm, and therefore the shared intrinsics, that the start compartment shares with these other compartments.
`); | ||
``` | ||
|
||
The new compartment has a different global object than the start compartment. | ||
The global object is initially mutable. | ||
Locking down the start compartment hardened many of the intrinsics in global | ||
scope. | ||
After lockdown, no compartment can tamper with these intrinsics. | ||
After `lockdown`, no compartment can tamper with these intrinsics. | ||
Many of these intrinsics are identical in the new compartment. | ||
|
||
```js |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid terms like "the property holds" below when you're not referring to object properties but in a context where you might be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any big problems beyond the ones Mark flagged. The docs changes are minor, and if we have a usable module-importing example on the way, I'm ok to see this land without demonstrating any sort of install-ses
scheme.
packages/ses/README.md
Outdated
that that object and any object reachable from its surface. | ||
The compartment can import modules and evaluate programs. | ||
SES introduces the `lockdown` function. | ||
Alters the surrounding execution environment such that no object intrinsically |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
grammar/readability nits: maybe:
- Calling
lockdown
alters the surrounding.. - .. no intrinsically-accessible object, like ..
- .
lockdown
also tames other ..
packages/ses/README.md
Outdated
import 'ses'; | ||
|
||
import 'my-vetted-shim'; | ||
|
||
lockdown(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, that shows off the simple one-file use case well. I'm on the fence as to whether my-vetted-shim
should be imported before ses
(to visually suggest that it is unconfined), or after (to remind readers that nothing is actually confined until lockdown()
is called).
But a more realistic usage summary would involve confined modules too. With the current feature set, that means writing an install-ses
module (consisting entirely of import 'ses'; lockdown();
), and doing the other imports before/after it:
import 'my-vetted-shim';
import 'install-ses';
import 'confined-module';
other_confined_code();
Do we want to show this here/now, or will the upcoming module-import support provide a better way? I'm hoping we'll have some docs that explain how to use this in a way similar to how folks are used to writing/running programs today, rather than putting off the practicalities in favor of a future application-loader/manifest/"larger-scale" ecosystem that we're still trying to figure out.
packages/ses/README.md
Outdated
@@ -74,8 +119,6 @@ c1.global === c2.global; // false | |||
c1.global.JSON === c2.global.JSON; // true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c1.global
or c1.globalThis
? I guess it depends upon whether #313 lands before or after this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the PR that resolves #313, so I’ll fix this here!
0e83a28
to
71b1d97
Compare
I’ve responded to most the great feedback from @warner and @erights and request review to the two new fixup commits I’ve added to the end.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but for one doc correction.
packages/ses/README.md
Outdated
{}).__proto__`, the properties of any accessible object, the return values of | ||
any accessible function, and the result of using any accessible constructor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the return values of any accessible function, and the result of using any accessible constructor.
Either I misunderstand you, or this is wrong. Calling a function or constructor can, and often does, allocate and return a fresh mutable object not locked down by lockdown()
. I think just deleting that part of the sentence fixes the problem.
Previously, SES exported
lockdown
. Callinglockdown
introduces a globalharden
.This change removes all exports and instead surfaces the same names on
globalThis
. That is,lockdown
,harden
,Compartment
, andModuleStaticRecord
are all globals as they would be if they were introduced by the platform rather than a shim.Fixes #301