l4-hurd
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: confinement with endogenous verification ??


From: Jonathan S. Shapiro
Subject: Re: confinement with endogenous verification ??
Date: Wed, 26 Oct 2005 22:20:25 -0400

On Thu, 2005-10-27 at 02:00 +0100, Brian Brunswick wrote:
> On 27/10/05, Jonathan S. Shapiro <address@hidden> wrote:
> > On Thu, 2005-10-27 at 00:35 +0100, Brian Brunswick wrote:
> > > called "endogenous" verification, in EROS and others, and potentially
> >
> > Close, but not quite. It is possible to compute whether a newly
> > instantiated subsystem will be confined at the time of instantiation,
> > and it is possible for the requester (the program requesting that the
> > subsystem be instantiated) to learn whether this will be so.
> 
> So is this confinement in some sense relative to assumptions about
> additional access? The program could then give the subsystem
> additional capabilities and change that? But nothing else can because
> it hasn't the capabilities to the new subsystem? (/Can/ capabilties be
> sent over IPC channels in EROS, or only at creation time?)

I am not entirely sure that I understand the question, but let me
attempt an answer and we will see how close we get.

The test performed by the constructor establishes an initial condition.
It guarantees that the initial capabilities held by the new process
divide exactly into two sets:

  1. Capabilities that are transitively read-only. These are harmless.
  2. Capabilities that were provided by the requester. These may not
     be safe, but the decision to grant them was made under the control
     of the requester. For this reason, they are considered to be
     "authorized".

What the verification proof shows is that if the confined system *ever*
obtains write authority to *anything*, then this write authority can be
traced back to a client-authorized capability.

Yes, the requester could later give the subsystem additional
capabilities. This does not change the fact that the subsystem is
confined, because these new capabilities are, by definition, authorized.

Yes, capabilities can be transmitted via IPC. However, an endpoint
capability is NOT considered to be transitively read only [exception: a
requester capability to a constructor is a specially recognized case,
and is considered safe because the constructor executes trusted code and
one constructor is able to recognize an endpoint to another]. It follows
that any endpoint capability must have come from the set authorized by
the requester. In consequence, any capability that is later transmitted
using an IPC is authorized by the requester's decision to provide the
endpoint in the first place.

I think the question that needs to be answered at this point is: "Is
this initial condition check actually important in practice?"

I think the answer is yes. If the initial condition check is satisfied,
then it is definitely possible for the requester to screw up, but it is
potentially possible *not* to screw up. On the other hand, if the
initial condition is unsafe, you are screwed up from the very first
step, and it is impossible to recover later.

In the overwhelming majority of real cases, subprocess relationships
follow hierarchies. Even when "peer" relationships are established, the
collection of peers tends to live "below" some dominating process in the
hierarchy. Under these conditions, the confinement check is particularly
effective.


Another way to look at this is much simpler: the initial confinement
check ensures that any peer communication channel among subsystems has
to be set up intentionally. It isn't inherited. It doesn't result from
an accidental inheritance across exec() or some such thing. In order to
set up a communication channel, a programmer has to write code on
purpose that sets up the channel on purpose.

This is a surprisingly effective barrier, because it makes natural human
laziness an ally rather than a source of vulnerability. In consequence,
it tends to lead to systems that default to safe configurations rather
than unsafe configurations.

Can you screw up? Definitely. But it becomes the exception rather than
the rule.

When you combine this with a design approach where

  (a) applications are divided into protected subsystems, and
  (b) sensitive content tends to live in *inner* subsystems,

you end up in a situation where several sequential screwups have to
align just right in order for something really bad to happen. There is
no way to "prove" this, but it really does seem to work this way in
practice.

One of the ideas in EROS/KeyKOS that I think is really important is also
really elusive. Yes, we have done all of this detail work and proof, and
all that is nice, but in practical terms the real power of the
architecture lies in the fact that things tend to default to safe
conditions, and getting out of safe conditions takes extra work.

> > The space bank authentication is used EVERY TIME we create a process. It
> > is utterly impossible to build a program that makes any guarantees about
> > behavior at all if it does not know that it has exclusive access to its
> > own storage. In the discussion, I described the protection of encryption
> 
> This is protection even against the thing requesting the creator of
> the subsystem - only the instantiator has the access, thereby
> protecting the new thing if is has special capabilities?

I'm not sure about the "special capabilities" part. I don't think this
is relevant.

Also, there is some confusion about the term "instantiator", so let me
define terms:

  Requester: the process that asks the "foo constructor" to 
     instantiate a new process that executes the "foo" application
  Constructor: the process that does the instantiation
  Yield: the process instantiated by the constructor.

I *think* that you mean the constructor when you use the term
"instantiator".

In EROS, even the constructor does not have access to storage allocated
by the yield. The constructor can (conceptually) destroy the bank (and
therefore all storage allocated from this bank), but it cannot access
the storage that has been allocated by the yield.

Similarly, the requester can destroy the bank, but cannot access the
storage allocated by the yield.

In practice, we do not worry about threats from the constructor, because
the code executed by the constructor is part of the system-wide TCB.

> BTW, this kind of thing seems awfully much like priviledge escalation
> to me - the much derided setuid applications!

Can you explain where you think the escalation is happening? The
constructor does not have special privilege because of any escalation.
All of its privilege derives directly from the capabilities that it
holds. The constructor does not have any special privilege beyond what
it's capabilities permit.

> > The confinement check is not used as much. The shell does this check all
> > the time, but most other programs do not bother except in special
> > circumstances, such as execution of untrusted code. This is true partly
> > because the test is transitive, so the check performed by the shell is
> > sufficient to cover an entire program.
> 
> Presumably it can't apply to any network accessing program at all.

If the requester has access to the network, and authorizes this access
to the yield, this does not violate confinement.

But "least privilege" applies here: it is the job of the requester not
to grant access promiscuously. The job of the constructor is simply to
ensure that if a screwup occurs, it wasn't a result of "hidden"
authority.

>  And
> confinement is relative - we can't know what kinds of secrets are
> actually present in the (small) set of files being handed to a
> program.... But then if there is no outward channel.... I guess it
> might come down to covert communication channels then. Real time
> issues!

Secrets are not directly relevant to confinement. Confinement is a
constraint on outward communication.

Yes, if you are concerned about covert channels, then you get into
real-time issues, and scheduling of multiplexing, and a long list of
other challenges. These do not have general solutions, which is very
troubling. There *is* some good news:

 1. The sources of covert channels can be reduced without altering
    the program.

 2. Capabilities cannot be leaked over covert channels. They can be
    proxied, but once the program that holds the capability is killed
    you know that the access is terminated.

 3. Covert channels do not allow theft of information. They only allow
    *disclosure* of information. English: we don't need to solve the
    covert channel problem in order to solve the virus and hostile
    plugin problem.

Because of covert channels, confinement of *data* is not absolutely
possible. Confinement of authority (capabilities) *is* possible. This is
why I feel that the primary advantage of confinement as the standard
tool for process instantiation is its effect on system structure and
developer habits.

> Once
> > confinement becomes a design consideration, programmers begin to ask how
> > to design their programs in ways that permit them to be confined. This
> 
> Minimum privs I suppose. Lots of good effects of course

Yes. Confinement and least privilege have many synergies.

> > > We might think of it as akin to so called "introspection" that is
> > > possible in the run-time systems of certain languages (java), and used
> > > to support dynamic loading and configuration of system components.
> >
> > Perhaps, except that the constructor check is performed statically, and
> > introspection is generally very problematic if security is a design
> > goal.
> >
> 
> Eh? I'm confused now. I thought it was dynamic. Or is it a
> verification of the code of the constructor? I guess I'm trying to
> become convinced of the utility of such a check as a dynamic thing,
> rather than just as some (automated) reasoning about code correctness.
> (like a statically typed language.) Duh... Ogg say automated reasoning
> /good/!

Oh crud.

The terms "dynamic" and "static" are very misleading here. Let me unpack
this one more level.

When the requester asks the constructor "Would your yield be confined?"
This is answered with a single boolean test. There is no formal
verification done at this point, and no expensive introspection-style
traversal of the capabilities that the yield will hold. The question is:
"When/how is this boolean answer computed?"

When the constructor is being fabricated, the developer (or the
installer) inserts capabilities one at a time. As each capability is
installed, the constructor asks: "Has my test been violated by this
capability?" If the test is violated, the boolean goes to false (i.e.
unconfined) and stays that way.

The tricky part is transitive access. If I hand you a read-only
capability to a node that contains a read-write capability, you have no
*direct* write access, but you have *indirect* write access, which
violates confinement. In principle, this could be handled with recursive
introspection, but EROS implements a simpler way.

EROS has an unusual access right, "weak". If you hold a weak capability,
and you fetch some capability through it, the fetched capability will be
downgraded to read-only and weak. The net effect is that no mutate
authority can be obtained starting from a weak capability.

The constructor actually checks that the "initial" capabilities are
read-only and weak.

So:

  1. The check is performed dynamically, but at constructor creation
     time, not at instantiation time.
  2. The constructor test is conceptually similar to a memory safety
     test. It does NOT rely on verification of code.

Perhaps the second point will help answer your concern about whether the
test is correct, now that you see how simple it is.


> What about verification of other sorts of things, space and time
> constraints, result invariants? Perhaps these are more naturally
> checked dynamically though.

These things require verification of code, which is far beyond what the
constructor attempts. In special cases, space and time constraints can
be handled with model checking.

The particular invariants that you mention are all things that want to
be checked statically, because you want them to hold for all possible
executions.

The really deeply hard things to deal with are what Norm and I refer to
as "durability" properties.

shap





reply via email to

[Prev in Thread] Current Thread [Next in Thread]