Closed michaelfig closed 2 years ago
In thinking about it, I would instead of makeProtectionContext()
use the name makeModuleHelpers()
, so that we could hang other useful context-dependent functions off of it.
I'm thinking a simple console
object would be useful for this kind of thing. It's pure-ish in the sense that it would carry no observable state (write-only). console
is useful to scope to a module. We could attenuate the global console
to add a prefix identifying the module, and not propagate any difficult-to-implement extensions.
... We could attenuate the global
console
to add a prefix identifying the module
something like that (along with filtering some control characters) is pretty important to prevent spoofing.
cf
Are protect
and suspect
alternatives to the proposed insulate
pattern of protection? I.e. are users expected to protect at the module boundary level with protect
rather than at the function boundary level with insulate
?
Are protect
and suspect
required for a program to be considered a valid Jessie program?
- Are
protect
andsuspect
alternatives to the proposedinsulate
pattern of protection? I.e. are users expected to protect at the module boundary level withprotect
rather than at the function boundary level withinsulate
?
Yes. That's the expectation. Do note that module-level bindings (not limited to exports) must be both const
and protect
ed in order to prevent the leakage of mutable state.
- Are
protect
andsuspect
required for a program to be considered a valid Jessie program?
I would assert this, but @erights has in the past suggested that we could admit Jessie programs that had the corresponding explicit harden
calls in them. I believe this requirement is better served and easier to explain by the protect
membrane, rather than a separate compilation step to automatically insert the harden
calls.
So, in my view, they are needed to be valid Jessie, and enforced by the 'use jessie'
directive. However, your runtime may do something different with its implementation of jessie-std
, such as not actually creating a membrane. Being looser in an implementation is tolerable, but I'd expect not to add more requirements for the developer-facing API to which Jessie modules must conform once we have some reference implementations.
The use of a membrane grants us the most future flexibility.
I would say the following module leaks mutable state (and is not valid Jessie code):
export const obj = { x: 10 }; // x is leaked mutable state
Is this the kind of exported mutable state that protect
is protecting against?
But isn't the following leaking essentially the same mutable state, as viewed from the outside, but is valid Jessie code (?) :
'use jessie';
import { makeModuleHelpers } from 'jessie-std';
const { protect, suspect } = makeModuleHelpers();
// obj is an object with a mutable `x` property
export const obj = protect((() => {
let x = 10;
return {
get x() { return x; }
set x(value) { x = value; }
};
})());
(disclaimer: I haven't read the implementation of protect
yet, so I may be misunderstanding its intended purpose)
Getters and setters are not in Jessie.
The idea of a method closing over some internal-to-a-function state is fine, because the state is not shared among clients of the module. An exported mutable slot (rather than a "maker" function that closes over state) can be a communications channel between otherwise unconnected callers. The module-level state is a violation of ocap principles, but the "maker" state is not.
Ah, I missed your IIFE. No, the IIFE call is not valid Jessie. Module-level definitions cannot be anything except for (hardened) pure data (of which lambdas are one kind of pure data). The only way for Jessie code to evaluate something is for an external client to call them.
Which leaves the question of how the makeModuleHelpers()
call is allowed. I think it can be the only exception to this rule, just as the destructuring of its return value is an exception.
I think I would prefer:
'use jessie';
import { protect, suspect } from 'jessie-std';
Which would make this exception to the rules less weird for Jessie writers. It should be up to the host for how to implement jessie-std
for a given module. The plain-Javascript implementation of jessie-std
would not necessarily be very nice, but still be possible.
I changed the summary and description to use the above instead of the (old):
'use jessie';
import { makeModuleHelpers } from 'jessie-std';
const { protect, suspect } = makeModuleHelpers();
Ok, that makes it clearer.
The Jessie readme says that getters and setters are in Jessie
But really these are syntactic convenience. Exactly the same thing is possible with explicit functions like getX
and setX
. But you're saying that arbitrary expressions are not allowed at the module level, so the IIFE is illegal -- that makes sense.
If the semantics of protect
from 'jessie-std' are important to the security guarantees that Jessie provides, how do you statically verify that protect
resolves to the correct implementation? E.g. that the jessie-std
library hasn't been spoofed?
A Jessie module is meant to have the same semantics when running as SES code, but SES doesn't guarantee any particular resolution of the module specifier 'jessie-std'
, especially in the face of Compartment import hooks.
If the semantics of
protect
from 'jessie-std' are important to the security guarantees that Jessie provides, how do you statically verify thatprotect
resolves to the correct implementation? E.g. that thejessie-std
library hasn't been spoofed?
I would say that if somebody has obtained your Jessie source code and is running it under an environment that doesn't implement protect
properly, they get what they pay for. Your Jessie code cannot be in any way privileged except for what is passed to it from other modules, so "you" (the Jessie module author) don't miss anything if "your" code misbehaves, it's the user of the Compartment API that does.
That's the flexibility of Compartments, and a good reason not to use them lightly if you care about correctness of the code that you run.
I have to get my head around that :-)
I think what confuses me is that protect
looks like it's effectively part of the language. It's required on the syntax level when declaring anything at the module scope (correct me if I'm wrong), in the same way that class
is required on the syntax level in JS to declare a class, for example. But the meaning of the class
syntax doesn't/can't change depending on the larger program in which the module containing the class is running, whereas the meaning of protect
can.
Perhaps it would help my understanding if I could see a description of the security guarantees that protect
gives us (i.e. why we need protect
at all -- what would be broken if Jessie didn't demand it?). Does such a description exist anywhere?
But the meaning of the
class
syntax doesn't/can't change depending on the larger program in which the module containing the class is running, whereas the meaning ofprotect
can.
Not really. The meaning of class
can change if you preprocess the sources before passing them to an evaluator. Compartment is an evaluator, so it is not surprising that it can "break" code that is passed to it for evaluation.
What matters is that the Jessie guarantees are maintained, relative to that host. Compartment is creating a new host, with potentially compatible or incompatible changes compared to the original JS host.
Perhaps it would help my understanding if I could see a description of the security guarantees that
protect
gives us (i.e. why we needprotect
at all -- what would be broken if Jessie didn't demand it?). Does such a description exist anywhere?
The wider context is how SES is based on object-capabilities. If you're not yet familiar with ocaps, I'd refer you to @dckc's excellent https://github.com/dckc/awesome-ocap
Closing as: explored but a different approach will likely be used.
@erights please review and comment. Please also tag anybody else you think should have a look.
I would like to propose an addition to the Jessie Standard Library #29: a
protect(x)
andsuspect(y)
function.The intent would be to use them as follows within a Jessie module:
The goal is the airtight prevention of accidental
this
-capture, also (as a side benefit) preventing the escape of any unhardened bindings/values.The Rules are:
protect(x)
orsuspect(x)
call on a primitivex
returnsx
itself.protect(x)
for an objectx
, callsharden(x)
, then returns a membrane so that when function properties such asx.f
are called, its inbound arguments are wrapped withx.f(...suspect(args))
then invokesprotect(ret)
on any outbound return (or thrown) value.suspect(y)
for an objecty
, returns a membrane so that when function properties such asy.g
are called, it first ensures that the outboundthis
value is not protected within the context defined by themakeModuleHelpers()
call, then its outbound arguments are wrapped withy.g(...protect(args))
then invokessuspect(ret)
on any inbound return (or thrown) value.protect
andsuspect
are used when necessary. At a minimum, all imported bindings/values must besuspect
ed before use (except for imports from@agoric/jessie
itself), and all module-level bindings (exported or not) must beprotect
ed (except for raw identifiers and the binding ofprotect
andsuspect
).