l4-hurd
[Top][All Lists]
Advanced

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

Re: new exec server protocol


From: Marcus Brinkmann
Subject: Re: new exec server protocol
Date: Tue, 20 May 2003 23:29:28 +0200
User-agent: Mutt/1.5.3i

On Tue, May 20, 2003 at 10:01:08PM +0200, Niels Möller wrote:
> Marcus Brinkmann <address@hidden> writes:
> 
> > I had a look at the exec server protocol, and it has two main issues:
> 
> Do I understand this correctly if the only reason exec is needed at
> all for normal, non-setuid, execs, is to get the task through the
> singularity where the task has no mappings?

Unfortunately my ideas where based on some assumptions that are now proven
to be wrong, so the whole thing collides.  I am not sure how much of my
ideas I can uphold, I am still pondering the possibilities.  In particular,
I am concerned about the exec server being involved in communication with
untrusted servers, but we need to somehow move the object handles from the
old task to the new one if they are not the same (ie, if it is not a simple
overlay but secure mode is enabled).  So currently I am thinking about
either task server support to complete the move or returning from exec so
the half-dead old task can send the handles to the new task which does a
short receive on start up.

There are definitely to mostly different cases:

1. New task.  This option is either explicitely selected, or implicitely by
secure mode.  In this setup, "a new task is created", this means that
everything that is not explicitely copied/moved to the new task is lost and
destroyed.  This is important so a program can not allocate resources (via
object handles) that are not released when execing a suid application.

So far, the only way we discussed that allows forcefully releasing such
resources are the task death notifications.  So, the old task must "die" and
be replaced with a new task.  The new task must get a lot of state from the
old task, including some object handles (representing file descriptors and
other stuff).

In Mach, you could insert object handles into tasks.  With L4 and
self-managed object handles, this is not as easy anymore, as you would
somehow authenticate yourself to the server that you are allowed to insert
an arbitrary send right (and I wonder how to do this, although by
cooperation with the task server it might be possible).

In the secure case, the exec server also has to care about other things like
how to find and fire up the interpreter.

2. No new task.  This is the default.  In this scenario, the user can only
screw himself.  If he doesn't release resources but forgets about them, then
that is up to him.  Eventually they will be cleaned up, either by a secure
or new_task exec or by killing the task otherwise.  In this scenario, we can
just keep other servers thinking that nothing changed: The task id will stay
the same, and no death notification (or something like that) will be sent out.

It's clear that the second case is the simpler one, although it also has
some interesting aspects.

Now, let's see what you suggest for this second case.

> Then I think a reasonable way to divide the work is to
> 
> 1. Have a library function (libexec, libbfd, wherever) that takes a
>    file port as argument and returns a new file port to the
>    interpreter file, as well as any modifications to the argument
>    list.
> 
>    Note that the most common case will be the interpreter ld.so. I'm
>    not sure how the double-interpreter case works, where a #! script
>    is interpreted by /bin/interpreter, which in turn is "interpreted"
>    by ld.so.

I am not sure if you suggest that this code is used in the exec server or in
the user's code.  There is a reason why I said that exec must receive the
initial request and return with the interpreter name.  The reason is that
you can run executables without having read rights.  So you can not parse
the executable to figure out what should be done, this is something only the
filesystem or the exec server (who receives the file port from the
filesystem) is allowed to do.  That is also why the canonical function in
the Hurd is file_exec (ie an RPC to the file server), and not exec_exec (the
RPC to the exec server).

This reminds me that there is a security hole there in the Hurd,
because if you keep a handle to the task port somewhere, and then do exec,
you can use the task port to read out the task memory.  So that means that
you can get at the content of an executable if you can execute it (unless it
is also suid).  So you need to set the "new task" flag when the file is not
readable to the user.  And in fact, libdiskfs/file-exec.c has code to do
that, but it is commented out because the proc server will give out the task
port anyway.  (I will have to keep this in mind, maybe we can fix that).

> 2. An exec server implementing a single exec rpc, that takes a file
>    port and an argument block as argument (the task of the caller need
>    not be an explicit argument, right?). The memory block is a
>    integral number of pages mapped into the server's address space
>    using the L4 ipc primitive.
> 
>    Next, it maps (or loads, if the filesystem doesn't do mmap) an
>    image from the supplied file port (this seems to involved rpc to an
>    untrusted fileserver. Is there any way around that, except by
>    getting rid of exec, as described below?).

Good point.  Also moving any object handle to exec is going to use that
server.  I guess that is something we have to live with, in particular as it
is not that bad, as if it is hanging, it is just the one thread hanging and
can be resolved by killing the requesting process.  I might have tried to
attempt the impossible when I sent in my first idea.

>    It also maps the given argument block into the new address space.
> 
>    It sets up a stack, copies some more information there, and starts
>    a new thread.

The task port is, among other things, needed to kill all threads in the old
task, and to unmap the whole address space.  I see that you suggest a
different approach below, but that is also bound to fail.

I don't think you can get rid of exec at this level, in particular as you
need it anyway for non-readable executables and suid executables.

> Next, let's see if we can get rid of exec completely:
> 
> Keep an exec/startup stub mapped into both the old and the new address
> space, and put the exec code there (it could be mapped at a fixed
> address in all processes, or it could be the responsibility of the
> task that wants to perform an exec).

You have no guarantee that there is such a place.

The only way I could see where you don't necessarily need exec is if you
always create a new task.  This might be cheap enough in L4 to do.  In fact,
I don't see why it isn't always done right now, except maybe for
friendlieness.  OTOH I am sure I have not figured out everything involved
into exec yet.

> As for setuid exec, I don't know. I'm inclined to say that it should
> be the responsibility of the filesystem.

It already is.  I didn't explicitely mention that as it just forwards the
request to the exec server.

However, your idea has charme.  Eliminating the exec server would mean
eliminating the RPC from exec to the (possibly untrusted) filesystem server.
Where a user can do exec himself (by creating a new task rather than by
having glue code in a small mapped region, I'd guess), it could be very
fast.

> This all makes sense to me, because setuid exec is a private affair
> between between a process and a file server. The process wanting the
> exec has to surrender all control, by handing over a task port (or
> equivalent) to the file server. The file server needs no L4 privileges,
> it only needs to be able to give away its own uid's to (cooperating)
> processes of its own choice, something which *every* process should be
> able to do.

Yes, this does make sense, and it is actually a separate matter if the
filesystem does the job itself or uses the exec server.

> Summary: exec is about managing your own address space. There's no
> reason for any privileged server. And if some other process (a file
> system) is willing to share some of its power, then you should be able
> to arrange that by a protocol with no third parties.

This is inspiring, and I will think if I can put this into a solid design
with the features we need.  However, I am still unsure about a couple of
issues, in particular interaction with proc, and if a new task needs to be
created or not.

Let's say for a moment that instead reusing your own address space, you
always create a new one.  That means that you can have both tasks exist in
parallel.  Before the new one can run, the PID (and other state) has to be
reassigned to it.  But then there is a short time where it might be possibly
allowed to have the old task running in a near-dead state that can be used
to transfer some data and handles from the old task to the new one.  In such
a scheme, object handles for inherited fds etc would directly go from the
old task to the new one.  This scheme would work equally well for the secure
and the not-secure case.  It would also work whether you have an
intermediate exec server or not.

I was too quick with my "new exec server protocol".  Another mistake I did
was that I wanted to set up the stack for the task to contain the data. 
However, the only way to do this in L4 is to have some pager and pager
interaction.  But then you can just as well use a startup RPC (in fact, that
is easier).  So, the idea of no-startup-rpc has to be scraped as well.
My delusion lasted only for a couple of hours :)

I am very happy about your comments, because they widened my view for every
possibility from no exec server at all to the full blown model :)  The only
think I know for sure so far is that more work is needed to get this right.

Very interesting discussion :)

Thanks,
Marcus

-- 
`Rhubarb is no Egyptian god.' GNU      http://www.gnu.org    address@hidden
Marcus Brinkmann              The Hurd http://www.gnu.org/software/hurd/
address@hidden
http://www.marcus-brinkmann.de/




reply via email to

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