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: Bas Wijnen
Subject: Re: The Perils of Pluggability (was: capability authentication)
Date: Tue, 11 Oct 2005 12:34:06 +0200
User-agent: Mutt/1.5.11

Hi,

First of all, when not mentioned otherwise, I'm talking about the Hurd, not
about OSs in general.

On Mon, Oct 10, 2005 at 09:11:37AM -0400, Jonathan S. Shapiro wrote:
> On Mon, 2005-10-10 at 11:08 +0200, Bas Wijnen wrote:
> > 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*?

Obviously I 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.

The properties of the library are described in the interface.  If they aren't,
then it's buggy and should be fixed.  As long as there are known bugs in my
program or one of its dependancies, I'm not making guarantees about the parts
they're in of course.

> 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."

But I'll test them anyway, so I hope to find most bugs which can happen
through my program.

> 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).

When real hard guarantees are needed, testing and auditing of all components
becomes important.  That obviously includes libraries.  In this example, a
written guarantee that the library doesn't mess up might be a good idea as
well.

> 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?

It knows by trusting the source of the capability.  The program is started by
some user.  If there are already keys available which should be used, then the
parent process needs access to those keys, otherwise the program cannot reach
them.  If there aren't, then the program should create this storage.  The
parent isn't neccecarily allowed to access it, but it could have done the
creation itself.  So if it chooses to start a child which doesn't give it
access, then it probably didn't want access to them.

If the parent has no access to any safe storage, then the child doesn't
either.  In that case, it would be wisest to not run the program at all and
ask the system administrator to allow such things.  The key point is that the
parent _knows_ in some way if things are safe, and therefore the client can do
what it should.  Assuming there is a proper bootstrap procedure, it follows by
induction that everything works. :-)

> 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".

Obviously.  I'll try to stick to "parent" instead of "creator", as that's a
much more common term.

> 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.

I was talking solely about the Hurd.  Of course it is possible to write
systems which work differently.  But in the Hurd, capabilities from the parent
process are trusted (at least the ones that are passed at process startup
time).  Other capabilities are not trusted in general, or at least not fully.
Of course the user may for example force passing an untrusted capability to a
child, which can make it a trusted capability for the child.

> 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.

Verifying wouldn't help here, as the capability to the physmem server is also
inherited from the parent, and they will match, so the verification succeeds
even if we are not using the top-level physmem server.  And that's a good
thing, as otherwise sub-Hurds (jails) would not be possible.

> 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.

In the Hurd we chose to not make this knowledge available.  Trusting the
parent process seems a good solution for all flexibility to me, without
compromising security.

> 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!

In the Hurd, the parent is by definition trustable.  Verification may be
needed for capabilities from other sources, but we should be very cautious
with them anyway.  However, most of the time they will come from trusted
servers, which means we should trust them (according to our parent, which is
always right :-) ).

> > 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 the Hurd, child processes are the responsibility of the parent.  So if a
child can mess up, then that is no security problem, unless it already was a
security problem with the parent (which could mess up as well).  If the parent
can mess up, but is trusted not to, then I would not consider that a security
problem.  Creating children which mess up is considered "messing up by the
parent".

This is not an argument for giving broad permissions to all trusted processes.
Trust should only go so far as is needed.  If a process doesn't need certain
rights, it shouldn't have them.

> 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.

There are two sources of capabilities for a process: From the parent, and from
third parties.  Any capability that can be aquired from a third party can be
aquired by the parent itself as well.  So this scenario is not possible in the
Hurd.

> > 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?

This is what I was thinking of, but it doesn't make sense:

S is a server which does things.  Let's say it's physmem and there is an
object which provides secure storage (for your encryption key example).  A has
a capability to S, but not to the secure storage object.

B (the "provider") claims to have a capability to that, but isn't trusted by
A.  It gives the capability to A.  Now A doesn't want to use it, unless it
knows that B's claim was true.  So here a verify operation from A with S on
the capability could be useful.

I don't know if such a situation would ever take place in reality though: such
capabilities would usually be given by the parent, not by an untrusted
partner.

> 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*.

In the above example, not only what it does, but also on which server, is
important.  A question "for which server is this capability?" seems
unneccesary to me, but "is this yours?" may be useful, I think.

> > 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.

passwd is started by the filesystem, which has access to /etc/passwd and
/etc/shadow.  It gets its capabilities for stdin/stdout from an untrusted
source (the process which runs the command, not the parent), but that's ok, as
it doesn't use them for any security-sensitive operations *that it's
responsible for*.  If stdin is monitored by some malicious process, then the
process which runs the command made that possible.  This is not the
responsibility of /bin/passwd, and there's nothing it can do about it anyway.
This is akin to running passwd over an unencrypted network connection.  While
passwd may behave well, there's still a huge security problem.

> Then try to explain it in a system that does not have set[ug]id.

There is no way to run Hurd and still be secure on such a system.  I do not
see this as a problem, it is simply a requirement for the Hurd.

> > 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.

Not in the Hurd. :-)  But if you disagree, please give an example.

> > > 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.

While this sounds interesting, I think we have enough to talk about already.
:-)  For that reason, I do not ask you to explain.

> 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.

I think they aren't either, that's why I wrote "in most cases".  This was not
meant as "always trust servers", but more like "in general, we should focus on
clients".

> > >   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.

When I wrote "external", I meant that it is beyond the control of the client.
So it cannot confine it, and doesn't have any guarantees about its behaviour.

> > > The current state of the art gives us only three mechanisms for dealing
> > > with this:
> > > 
> > >   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.

Ok, that makes sense.  But in the end, all this comes down to "the user" IMO
(where I assume the user can go to the administrator and have things installed
if she wants to).  The architect of the application can be bypassed by using a
different application. :-)

> 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.

That sounds good.  Forking and dropping (almost) all capabilities seems easy
enough for me.  However, this is Hurd-specific code, which is not good.  There
should be some library for such things I guess.  Then people can easily use it
and still be portable.

> > 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.

I very much like the UNIX approach: Be secure by default, be flexible by
request.  The request often comes in the form of --force.  If someone messes
up when using --force, I don't think the developer is to blame.  I am very
much against limiting possibilities because they can be abused.

> > > 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.

So you want some version checking of the interface?  That sounds reasonable,
if it's meant against unintended errors.  It shouldn't prevent intentional
interface changes though.

> > >   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.

I don't have a clear picture of what you mean here.  Could you clarify a bit?

Thanks,
Bas

-- 
I encourage people to send encrypted e-mail (see http://www.gnupg.org).
If you have problems reading my e-mail, use a better reader.
Please send the central message of e-mails as plain text
   in the message body, not as HTML and definitely not as MS Word.
Please do not use the MS Word format for attachments either.
For more information, see http://129.125.47.90/e-mail.html

Attachment: signature.asc
Description: Digital signature


reply via email to

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