Comments on Lex's "Object as Capabilities in Squeak"
Mark S. Miller
markm at caplet.com
Wed Jan 29 22:37:06 UTC 2003
[cross-posted to squeak-dev and e-lang]
In rereading Lex's paper, Anthony's paper, and the Squeak-E thread on
squeak-dev, there's a lot to say. I'm tempted to just jump right in and try
to talk you guys out of dynamic scoping, just as Norm talked Dean and I out
of it in the Joule-design days. I do think this is the most important
remaining issue (though not the last one), but I think I'll instead build up
to it slowly. It took Norm a long time to convince us, even when we already
had a large base of common premises. I think I should first see what
premises we all do have in common, and what premises we can come to have in
common. I will start by commenting on Lex's paper. From there, I expect to
go on to Anthony's, and then to the email thread.
On first reading Lex's paper, I assumed that we were all trying to solve the
same set of problems, and that Lex (and squeak-dev) has not yet realized the
incompatibility costs of addressing these. On rereading Lex's paper, I think
this is still probably true, but it strikes me that y'all may in fact only
be interested in a genuinely less challenging subset of these problems, in
which case y'all can plausibly address these at a much lower incompatibility
cost. In any case, none of us can make informed choices until we understand
what these tradeoffs are. This will be new exploration for me too, as so far
I've mostly only thought about the more complete problem.
All unattributed ">"-style quotes are taken from Lex's "Objects as
Capabilities in Squeak", Aug2000 version, at
For a first attempt at a taxonomy, let's try the following four levels of
1) Applet-like security:
>In order to widely share Squeak pages across the Internet, it will be
>necessary to engineer a safe display environment--a sandbox--for such pages.
I've never heard of "Squeak pages" before. (My apologies again for my
ignorance of Smalltalks post PPS2.5.) However, from the meaning I'm
guessing, this sounds like a statement of purpose that could as well have
been stated for Java applets. I would characterize this as:
Instantiate safe "mobile" code. The applet-like instance can render into its
window, receive ui events and interact with the user, but cannot exercise
any of the user's authority nor modify any of the user's persistent state --
beyond the state understood to be part of the applet-like instance itself.
The problem with applets is, they're useless. That's why you don't
hardly see any. Their uselessness is precisely derived from a) their
starting position of lack of authority (good), and b) the inability to
incrementally and dynamically authorize them--well after they're
instantiated--to do some useful job (bad). (See #2 below.)
For those seeking only this level of ambition, I see nothing at all wrong
with the mechanisms Lex proposes. (This is *not* to imply that these
mechanisms aren't fine at higher ambition levels, or to fault Lex's paper
for not articulating a more extreme goal-set. It's taken me decades to go
from working on these problems to being able to explain what problems I'm
solving, and I still feel like I haven't reached the halfway point on that
journey.) I point this out so we can keep track of ambition-architecture
correspondences as we go.
When I say "I see nothing wrong", I'm referring specifically to Lex's
paper. In particular, please don't take this remark as an endorsement of
dynamic scoping (currently being discussed on squeak-dev) even for this
first level of ambition. The paper transforms globals into per-process
variables, where a process serves much the same function as a ClassLoader
unit (the ClassLoader, all the classes loaded by that loader, and all
instances of those classes) in Java's applet architecture. Either of these
mechanisms would be candidates for the least incompatible change to
Smalltalk in order to rescue "globals", rather than replace them. If
relevant objects are partitioned between such processes (as I take Lex to be
mostly trying to do), then the two mechanisms may even be close to
equivalent. If y'all's ambitions really do stop here, it would be good to
argue their relative merits, but I'm hoping we won't need this argument.
(If the relevant objects cannot be so partitioned, such that the same object
can be accessed from multiple such processes, then I would indeed argue,
even for this ambitiion level, that process-scoping is wrong while loader
scoping still looks acceptable. But I'll wait on that argument until it
OTOH, Dynamic scoping goes well beyond trying to preserve compatibility in
order to rescue old code, and instead introduces a new linguistic concept
substantially more complicated and questionable that per-process-scoping or
loader-scoping, solves no further security problems, and moves Squeak away
from the idea of object oriented programming. But I'm getting ahead of
2) Caplet-like security, one level deep:
As safe as applets and as useful and usable as applications.
Lex's reference to "pages" and the text spent on UI access leads one to
expect that the user is able to operate the ui of untrusted code --
applet-like so far. In order to get beyond the sterile uselessness of
untrusted Java applets, the user needs to be able to grant a caplet-like
instance further specific authorities, as appropriate in order for the
caplet to do some useful job on behalf of the user.
Authorization must happen by intuitive user interface actions, enabling the
user to "naturally" (without thinking they're thinking about it) maintain a
model in their heads on the bounds of authority they've granted to different
caplets, so they can naturally reason about the bounds on risk to themselves
when considering another act of authorization.
See http://www.sims.berkeley.edu/~ping/sid/ for an elaboration of these
principles, and http://www.skyhunter.com/marcs/narratedIntros.html for a
concrete system with UI (CapDesk) that implements 8 out of 10 of these
principles. (Crucially, CapDesk turns the UI into a capability system using
the same philosophy we're applying at the language level: turn each act of
designation into also being the corresponding naturally-implied act of
authorization. The user does essentially the same things he normally does.
These user-interface actions are now understood to also have the security
meaning that one would naturally expect. This is a bold claim. Look at the
pages to see the system in action.)
The above systems go out of their way to look conventional while obeying
these principles. It would be a very different, and quite exciting,
investigation to apply these principles to Croquet.
Before speaking to what any of this might have to do with the architectural
issues being discussed, I'll first wait to get a sense of whether y'all are
interested in pursuing these overall goals.
3) Privileged security abstractions
4) Unprivileged security abstractions
By a "Security abstraction" I mean something like the revocable forwarder
shown as Figure 6 on page 6 of http://lfw.org/ping/capmyths/usenix.pdf .
Such a security abstraction often stands between several different
interests, receiving and manipulating authority on behalf of each of them.
Such abstractions are also "deputies", as explained starting on page 11 of
the same paper.
Modern CPUs have a "privileged mode" and an unprivileged, or "user" mode.
E is built on Java. Java code in the E implementation is like code run on
the privileged instruction set, and E code is "user code". Lex has a similar
distinction between "privileged" and "unprivileged" Smalltalk code, and I
suspect he has to. No matter how similar these are made, they constitute two
separate languages which need two separate names. Let's call then "Squeak"
and "SafeSqueak". Unconverted legacy code that remains in the image is
Squeak code, all of which is in the TCB.
By "privileged" security abstractions, I'm attempting to define an
intermediate level of ambition that may require less principled support.
The issue is, how complete a language is SafeSqueak? It's easy to make it
expressive enough to do applet-like things (#1 above). It's next easiest to
also enable it to accept and use authorizations reified as
capabilities-as-objects (in support of #2). But is the system such that
SafeSqueak objects may find themselves between other SafeSqueak objects,
each representing different interests? Is SafeSqueak a good language
for writing security abstractions as deputies, in order to bring about
cooperation without responsibility in such a system?
If the answers are yes, then we're in #4. Otherwise, different unprivileged
interests may still be represented by SafeSqueak code, but the security
abstractions that stand between these interests are all Squeak code. In this
case, we're in #3. To determine which of these apply, a good exercise is to
try to rewrite the money example from
in SafeSqueak. I would like to see Squeak evolve into a system that can
support #4. I would hope y'all do as well. Only with #4 can SafeSqueak be a
self-contained language, and Squeak become a shrinking legacy.
A note on consensus:
Above I've repeatedly talked about finding out what "y'all" want or believe.
I don't imagine y'all are a collective, any more than I imagine e-lang is.
I'm just referring to the sense of the community that I hope to gather from
In the section
>The ObjectInspector Capability
a set of methods are listed. #realBlockFor: is missing, as it is mentioned in
>The ObjectInspector Capability
>realBlock := ObjectInspector realBlockFor: aRestrictedBlock
The ObjectInspector design seems sound, as a super-powerful part of the TCB,
to be closely held within the TCB, in order to build things like privileged
debuggers. (Unprivileged debuggers are hard, even in #4 architecture! KeyKOS
and EROS do it. E does not.)
It seems to me that ObjectInspector can be refactored in a way that would
make it both more convenient, and would naturally subdivide authority,
simply by currying it. I assume that the listed methods of ObjectInspector
are class methods, as shown by the above quoted use. Let's say that all
these were instead instance methods, and that ObjectInspector instead had
one instance variable and one "new:" class method. Then, instead of writing
x := ObjectInspector instVatOf: obj at: i
x := (ObjectInspector new: obj) at: i
The cool thing is that (ObjectInspector new: obj) evaluates to an object
giving encapsulation-breaking, meta-level access only to the state of object
"obj" rather than the system as a whole. This is a small but crucial step
toward non-privileged debuggers, if you wish to get there eventually.
In the section on Literals, you seem to use immutable and read-only as
synonyms. I find this confusing. To me, "read-only" means I can observe the
current state but modify it. It doesn't mean the state won't change. And if
the state to which I have read-only access does change, I expect to be able
to observe the new state.
Do you indeed mean "immutable" everywhere you say "read-only"?
The section "Dynamic Variable" seems to be to explain only per-process
variables. Is this right?
Could someone explain about "World"?
This section refers to "methods that are marked as privileged". Even after
reading the later section that explains this, I didn't feel like I
understood how this marking happens, or how the authority to do this marking
>are still be able to access variables
Can the same dynamic-variable-using instance be invoked from different
processes (causing different bindings of the same use-occurrence of the same
variable), or are dynamic-variable-using instance partitioned among the
processes that can invoke them. Unfortunately, I suspect it's the first, but
I'll wait to find out before arguing against it.
>Whenever code is loaded from untrusted sources, it should be loaded into
I'm not sure how to read this. If the code includes methods that were
written assuming they would run privileged, what happens?
>Processes with direct access to
I don't understand what this means, but it alarms me. In a capability
system, we should be speaking only of objects having access to other
objects. I know that Processes are objects, which is good, but I don't think
this accounts for what you mean.
Are restricted classes (as implemented by the restricting proxy) read-only?
(I mean, genuinely read-only, not immutable.) I think they should be.
> In particular, many class methods return "self"
This reminded me that Smalltalk methods by default return self. E had a
different but similarly dangerous policy. We found to our terror that this
was a pervasive source of accidental security holes in our code, very much
along the lines of the specific security hole you found with classes. Rather
than making a custom repair for each individual case, I fear that you'll
find you'll want returns-to-SafeSqueak to return null, rather than self, by
default. This is the first issue I've encountered that makes Java easier to
tame than Squeak. In Java, methods are void return by "default". ("default"
is a funny word. It's still explicit, but is the path of least resistance.)
>required to allow access one of these
Typo: insert "to"
>An incomplete list of such methods is the following:
I would be very interested to see the complete list of methods on Class,
ClassDescription, Behavior, and Object that you consider safe. It's much
more important to review the list of what's allowed than the list of what's
disallowed (though you made the right choice of which to list for the paper).
I don't understand why you allow instaVarAt: and instVatAt:put: on
non-proxies? Does this include non-proxies written in SafeSqueak?
I completely did not understand the section "Characters and Symbols",
probably because of my ignorance of modern Smalltalks. Could you expand?
>This proxy might or might not immediately install the cursor as requested,
>depending on the precise security policy that is being implemented.
Implemented by whom, how? This is the first I've seen of "security policy"
used this way.
I would hope to convince you that shared-memory multi-threading, locks,
semaphores and such should not be part of SafeSqueak. But, scoping and
partitioning issues aside, this is a mostly separate discussion we can leave
till another time.
You don't actually explain what the issue is with Exceptions.
>In particular, the following primitives should be disabled:
>* instVarAt: and instVarAt:put:, because they allow directly breaking
>* at:, basicAt:, at:put:, and basicAt:put:, if the proxy has indexed fields,
> because they would allow directly breaking confinement
I think I understand the others, but what's the problem with #at: and #at:put: ?
>Additionally, #shallowCopy and #clone make revocation much more difficult;
>thus, they should most likely be overridden to return the receiver instead
>of returning a true copy.
If you can't support their contract (and indeed you can't), then shouldn't
you just suppress them?
>Note that all non-primitive methods from class Object may be safely left
>accessible. Since such code must consist of message sends between
>parameters, self, and globals, user code could emulate the code even it it
>were disabled, and so disabling such methods gives no gain in security.
Are all the globals accessible from methods on Object necessarily accessible
by both callers and subclasses?
>A primary advantage of the submemories approach is that there is no need to
>add a special kind of cross-memory oop
Given the stated purpose of submemories, you need to be able to reclaim a
submemory without being able to reclaim the parent memory. This means that
oops from the parent into the sub need to spontaneously seem to become some
innocuous object (like null) when this happens. I think you will find
support for this hard at fine-grain. E supports this only between vats,
where there's an enforced indirection through intermediate objects anyway,
and where the possibility of partition is part of the semantics anyway.
In any case, I recommend postponing further worry about resource controls
and denial of service until these other issues settle down.
Text by me above is hereby placed in the public domain
More information about the Squeak-dev