l4-hurd
[Top][All Lists]
Advanced

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

Re: The Perils of Pluggability (was: capability authentication)


From: Jonathan S. Shapiro
Subject: Re: The Perils of Pluggability (was: capability authentication)
Date: Mon, 10 Oct 2005 09:11:37 -0400

On Mon, 2005-10-10 at 11:08 +0200, Bas Wijnen wrote:
> Hello,
> 
> On Sun, Oct 09, 2005 at 01:23:29PM -0400, Jonathan S. Shapiro wrote:
> > On Sun, 2005-10-09 at 10:14 +0200, ness wrote:
> > > I guess one of the design goals of the Hurd is to NOT depend on the
> > > implementation of a server. As far as I know, we don't want to ask "is
> > > the implementation of this server trustible?" but ask "is the source
> > > where I got this cap trustible?". We want to allow the user to replace
> > > system components. To e.g. run a new task that uses a different proc
> > > server. So the user says that to it's shell and the shell gives the
> > > right cap to the newly created task. But marcus identified sth. like
> > > your "identify" operation as necessary, AFAIK.
> 
> I don't really see the need for identify.  The current (AFAIK) idea we have
> for the Hurd is to trust the creator of the process.  The creator will give
> you your initial capabilities, and those are trustable be definition.  For
> that reason, the creator of setuid processes is the filesystem, not the user
> executing them.

You are saying two things here. Let us take them up separately.

First, you are saying that you do not see a need for "identify". Let me
try to answer that.

Suppose you write a program. Your program relies on a library. When you
test the program, do you test it against the *interface* of the library,
or do you test it against the *implementation*?

I suggest that interfaces don't run code. Implementations do. In order
for you to make any concrete guarantee about your program, you *must*
know the properties of the components that it relies on. Not merely
their interfaces. You must know their concrete behavior.

This is not an argument against interfaces. Often, you will say "I will
rely on the library designer's competence to build a test suite and not
break behavior." If you do this with a text editor, there is no big
problem. If you do this with the software on a pacemaker, you had better
hope that the victim dies when the bad upgrade hits, because if they
don't, you certainly will (figuratively speaking).

Let me go back to a concrete example that I gave earlier: I want to
build a program that keeps secrets. For example, it stores cryptographic
keys. To do this, it must place this data in pages. In order to know
that the data is safe, it must know that these pages are exclusively
held.

Now: I hand this program a capability to some server that implements the
storage allocation interface. How does this program know that the pages
are exclusively held if it does not know the implementation of the
storage allocator?


Your second assumption is that programs trust their creators. I believe
that by "creator" you mean to say "the program that instantiates them",
as opposed to "their developer".

It is often true that subprograms trust their instantiator, but it is
not always true. In EROS and Coyotos this assumption is not necessary.
We have a number of programs that wield capabilities that their users do
not (and must not ever) possess. The ability to use and guard these
capabilities against a hostile creator is one of the foundations of our
security.

These "suspicious subsystems" do *not* trust capabilities provided by
their creator. They verify them. In particular, almost *all* of our
programs test their space bank to learn whether it was a valid space
bank.

Trust in a capability is not a function of the source of that
capability. It is a function of knowledge of behavior. Trust in the
source of the capability only becomes interesting when knowledge of
behavior is impossible or unavailable.

I do not propose that we should abandon trust in the source. I propose
that stronger contracts are feasible, have essentially no cost, and are
very useful in practice. These stronger contracts let us reduce our
dependency on capability sources. This is good, because occasionally the
source of a capability will turn out to be hostile! Trust, but verify!

> This trust may give processes more rights than an external observer thinks
> they should have.  But if that is the case, then their parent process made it
> possible (and could have prevented it).  And since the parent process can do
> all the malicious things itself if it wants, I don't see why there's any
> security problem here.

Two responses:

1. You appear to be saying "because the parent process can make a bad
choice, there is no security problem."  This is like saying "because a
programmer can choose not to call gets(), there is no problem with
gets(). In a pure sense this might be true. I do not agree, but it is a
sustainable argument. Even if it *is* true, it is still better to remove
gets() from the library, because we *know* that use of gets() is a bad
idea.

2. If the service process has capabilities that it does not get from the
parent, then the parent *can't* do all of the malicious things that the
service might do. This turns out to be a very useful design pattern.

> Ok, I just remembered that there may be a need to check if a capability
> implements a certain interface at a certain server.  If the provider of the
> capability claims that it does, and the server is trusted, but the provider
> isn't, we will usually want to check the provider's statement before using the
> capability, to prevent doing things we don't like.

I do not understand the distinction between "server" and "provider"
here. Can you clarify?

By convention, EROS does provide an operation on (nearly) all
capabilities: getAllegedType(). This returns a unique identifier for the
interface type. Note, however, the word "alleged". Just because a
process *says* that it implements an interface does not mean that it
*does*.

> > Yes. I had some of this discussion with Neal and Marcus at LSM. The
> > problem with this design goal is that it is unachievable in a robust or
> > secure system. I understand that it is not a goal for Hurd to be secure
> > in the sense that Coyotos tries to be, but if you achieve your goal
> > above you will manage to be *insecure* in the way that Windows is. We do
> > not need another Windows in the world.
> 
> I don't see where security is lost if you trust the creator of your process.

Please explain how /sbin/passwd would work in this model. Then try to
explain it in a system that does not have set[ug]id.

> You must in the end get your trusted things from somewhere, and I can't think
> of a more appropriate place.  And since you don't have more rights than your
> parent (more likely you have less), there's nothing you can do that your
> parent couldn't do already.

But you often *do* have rights that your parent does not, and there
sometimes *are* more appropriate places.

> > I would like to propose for consideration a new social objective for
> > Hurd: Hurd should be a system that my mother (or your mother) can use
> > without fear. When something goes wrong, or something is compromised,
> > the damage should be well contained. The user's naive assumptions about
> > what is safe should be mostly right. I want to propose that this social
> > goal should be more important than arbitrary and unmotivated
> > flexibility.
> 
> I agree with that, but I don't see why that would be impossible with our
> approach.

I think I can identify a few technical obstacles at the moment, but I
also think they can be fixed. I would not propose this objective if I
did not think it could be met.

> > Suppose I hold an IOstream capability. What do I actually hold? I hold a
> > capability naming some body of code that *alleges* to properly implement
> > the IOstream interface. Without more information, here are some things
> > that I do not know:
> > 
> >   1. I do not know that it *actually* does these operations. The
> >      interface is an approximate statement of expected behavior,
> >      but it is *very* approximate, and even if the desired
> >      behavior were fully captured there is no way to check that
> >      the program behind the interface actually *does* this behavior.
> 
> There is no way to check this, unless we have access to the code (and even
> then it's very hard).  I think it's a Good Thing(tm) that the client cannot
> access the code of a server.  

Actually, it does *not* require access to the code. I agree that access
to the code from the client should not be required.

> In most cases, the server is trusted by the user
> (not the process) and client may not be.

You are making a large number of assumptions about client/server
relationships in the overall system design. These may be true in Hurd.
They are not true in object systems more generally, and I *suspect* that
they are not always true in Hurd.

> >   2. I do not know that information sent on this stream will remain
> >      private. The implementor of the IOstream interface could very
> >      well broadcast it to the world.
> 
> In case of an external untrusted server, this is neccesarily the case.  I see
> no other way...

I think you have not yet considered the role of confinement. All of the
properties that you identified for a library implementation can be
achieved for a completely untrusted service, provided the instance of
the service is confined. The confinement check can be done without code
inspection.

> >   3. I do not even know that a call to IOstream_read() will be
> >      returned.
> 
> As I said, this is acceptable.  It can be handled with a timeout (although
> that is fragile and may fail under heavy load), or by killing the whole
> process (including parent).  The latter may not be acceptable in some cases,
> but in those cases I think untrusted code shouldn't be used at all.

I agree that both solutions are technically feasible. The timeout one
should be avoided wherever possible, because it is untestable and it
causes the behavior of the application to change in response to system
load in unpredictable and unexpected ways.

> > The current state of the art gives us only three mechanisms for dealing
> > with this:
> > 
> >   1. Verification: perhaps we can verify some of the properties of
> >      the implementation. We commonly do this in Java as a check of
> >      memory safety, but doing this more broadly is well beyond what
> >      we know how to do for general programs. My lab is working on this,
> >      but it's not really relevant for Hurd today, so I won't talk
> >      any more about it.
> 
> This sounds interesting and unachievable to me in most cases. :-)

Yes. Even in the Coyotos effort we consider this deep research. We are
hopeful that a limited number of critical programs can be verified, and
that these critical programs are sufficient to bootstrap trust. We do
not believe that verification will be a general-purpose tool for
programmers during the current century.

> >   3. Risk: we recognize that we have no reasonable basis for trust,
> >      and we decide to use something anyway. The key to this is to
> >      arrive at a system architecture where risk is survivable.
> 
> This is up to the user, not the process IMO.

It is up to a combination of parties:

  + The user who decides what to run
  + The administrator, who decides what is *available* to run
  + The architect of the application (in your example, xmms), who
    decides what plugins they will run and what environment the plugin
    will run within.

Actually, I think you have given a very nice example. All I really want
for this example is a system architecture that makes running *confined*
plugins so easy and efficient that it becomes the default approach used
by developers.

> I think this is not what you mean.  It is the user which takes the risk, not
> the process.  IMO no process should ever decide it can take a risk.  Only
> users (and sysadmins) should.  And when a process is told it can take the
> risk, then it is a (partly) trusted partner.

I disagree. The developers of programs make de facto promises about the
robustness of the program. They have a right to select which plugins
they will run, and they express this right by building processes that
make this type of decision all the time. The design tradeoff is between
reputation for robustness and reputation for flexibility.

> > In general, pluggability must not be opaque: if you change a
> > contract that I rely on, I need to be able to detect this.
> 
> You mean if the implementation of the interface changes?  I do not see the
> difference between having an interface which was defined from the start as
> "I'll do A, but after 15 minutes I'll be doing B" and not changing it, and
> "I'll do A", and after 15 minutes the implementation is changed into "I'll do
> B".  I can understand that it matters for verification, but I'm assuming here
> that that's not possible.

The difference is that the first one is testable and broken. You can go
to the implementor and demand a fix, or you can replace the broken
component. The second happens *after* your program ships, and it
violates your dependency relationships.

> >   2. The consequences of failure are manageable. For example, the
> >      risk of introducing a new audio CODEC is acceptable because the
> >      worst that happens is you kill the player.
> 
> Even that is not possible if the player doesn't trust the plugin, see my xmms
> example above.

Actually, this is exactly the scenario that we *can* make manageable if
we can do identify on a small number of very low level services.



shap





reply via email to

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