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: Wed, 21 May 2003 23:05:18 +0200
User-agent: Mutt/1.5.3i

On Wed, May 21, 2003 at 04:27:24PM +0200, Niels Möller wrote:
> > On Wed, May 21, 2003 at 10:04:55AM +0200, Niels Möller wrote:
> 
> > > But if we do that, does it matter exactly
> > > when the old pid is transferred to the new task? It may be simpler to
> > > do it early, like
> 
> > You want to do such things as late as possible, when you have established
> > that the exec won't fail (or it is too late to recover).  I am not sure
> > if that's the point above.  In particular, if we hang in waiting for the
> > startup message, it would be nice to make this interruptible, so ^C can
> > recover from a hanging exec.
> 
> Good point. One needs to be careful to ensure that both tasks agree on
> whether the exec failed or not, so that exactly one of the tasks
> survive.

Yeah, although it must be cooperative, ie, if they disagree, then there is a
problem.  This is actually why proc currently kills the old task at a
proc_reassign RPC.  If the new task does the operation, we can retain this
behaviour, and things will work out for themselves.  (There are also things
like spawn operations where you want the new task to get a new pid of
course, such variants still remain possible).
 
> > > | Send startup          Receive startup (transfers object handles,
> > > |   message               message        including <proc, 4711>,
> > 
> > This would be more like
> > 
> >                                     Send startup message
> >   | Reply startup message
> >                                     Receive reply
> 
> This *is* a quite complex exchange, involving the task server and
> other tasks, as it transfers a lot of object handles.

Yes.  But it is an exchange between two user tasks, and thus we don't care
if it fails, hangs, or empties the fridge.  We must ensure that nobody else
is involved in the last startup message that transfers object handles and
such.  I think we achieved that.

For the messages between fs and new task, I originally thought that it would
be nice to do it a non-blocking, robust and secure way.  This is not too
hopeless, as some messages are guaranteed to arrive (the one you reply with
the IP and SP, then the first page fault), so the mapping of the file into
the task can be done without blocking.  However, the very first message with
auth handle and things like that are near impossible to get rid of

 (skip on first reading:
  just in case this line of thought is interesting or useful to anyone:
  A new feature could be added that allows you to insert object handles
  into other tasks if you have the task port - this means that you can
  make other servers to accept messages from that task without that task
  doing anything for it - please note I am not sure how this could be
  implemented - and then the fs server could insert the auth handle and
  let the old task send the actual auth handle number in the startup message
  - if the old tasks is malicious, then the worst thing that could happen
  is that new task doesn't find its privileges - however, I am not sure
  you can consequently get rid of all content of the fs-to-newtask message
  that way securely, even assuming the insert-handle stuff could be designed
  and implemented appropriately).

and I decided that I don't care anyway: The new task can stall the fs server,
but this is not so bad because the content of new task is defined and
controlled by the fs server itself (or by the root fs which is trusted
for interpreters).  If it is a problem that users can make files suid and
thus could arrange it that some file_exec operations block indefinitely
and thus occupy server resources, then we could still do it the way you
suggested and load up some glue code in the program that performs the
initial fs server hand shake and makes the information accessible to the
real program.  This would then be really robust and secure.

> > > Actually this proc registration is a little fuzzy to me. [...]
> 
> > A pid port itself only says you have control over the process it doesn't
> > mean you are the process.  However, I have not yet studied the proc server
> > in great detail, and there are certainly some crooks because of POSIX
> > requirements etc.
> 
> I guess the proc registration should at least say which thread is the
> process' signal thread, i.e. the thread that proc should send
> signal-related rpc:s to.

This could also be done at a later stage.  Proc has several such
initialization calls, and I have no clear picture on that yet.  However, I
am happy with saying that the new task should steal the PID using a pid
handle from the old task, and that will work out ok.
 
> > One important new issue (which forced me into some requirements for the
> > protocol) is that of holding a reference to task IDs.  For example, a
> > created task must automatically get a reference to the task ID of the task
> > that created it, so that it will know for sure that the startup message
> > will reach its intended target.  (How to get that thread ID of the creator
> > is another issue, I don't know where to put it - on the stack?  Maybe the
> > task server should provide a bootstrap handle - a simple uninterpretet 
> > word).
> 
> But it makes a lot of sense to at least have the task create rpc (to
> the task server) return a reference to the task.

That's right, I was overemphsizing that point.

> It seems reasonable
> to also install a referense to the old task into the new one, although
> that seems a little harier. I know too little about task startup in
> L4. The thread id can be put somewhere in the initial page granted to
> the now task, I guess, but it also needs some bookkeeping in the task
> server.

Well, of course, but that bookkeeping is needed anyway for secure IPC.  I
hope you still remember my last mail about handle exchange.  Holding
references to task IDs (so they are not recycled early) is absolutely
criticial in many operations, and this is one of them.  If the new task
contacts what it thinks is the old task, a malicious user who just happened
to replace the old task could steal the other task and wreak havoc with the
accounting mechanism.  Now you might say that this is only the user himself,
and this is not a security issue per se.  But for robustness it is
desirable, as the user process might just have been SIGKILLed for some
reason.

> I think this problem should be solved as a part of the task create
> protocol.
> 
> > For the suid case, this is problematic, as the filesystem server can not
> > give the old task the task handle, but it also can not communicate a
> > reference to the old task without race or complicated upcalls (I pondered
> > several options and they all sucked).
> 
> Who will be talking to who, in the suid case?
> 
> 1. Old task calls file_exec on the file server.
> 
> 2. File server calls task_create in the task server, and sets up the
>    initial page and thread (as the file server is going to give uid:s
>    away to this task, it better have full control over it's creation).
>    The user has to trust the file server, just as with any other exec.
> 
>    Note that the new task gets the file server's accounting id.

Good point (I thought of that earlier and forgot it again).  Is this what we
want?  I think that even for suid applications, you want to account this
with the user who started the programm, and not the user who provides the
filesystem.  I am unsure, though, as this is a border case.

If accounting with the old task is desirable, this further illustrates why
the user should allocate the resource (the task) and not the filesystem.
The filesystem is providing a service to the user already, it doesn't need
to give away other resources like tasks as well.

(The task_revoke call would be used to secure this principle).

> 3. New task and file server performs a startup-message exchange (which
>    may use different conventions than the ordinary exec, it includes
>    selected auth handles from the fileserver, but no pid)

This will contain entry point, program header information, stack base and
size, and things like that.
 
> 4. New task returns a startup-handle to the file server (or the handle
>    the fileserver has could be reused).

I am not sure we want handles at this point, but we will probably just use
simple thread ids and send messages from one thread to another (and only
that thread is allowed to).  OTOH, moving handles is required anyway, so it
might not be more work to do it correctly.

Note that for the file server this is secure as it has a handle, while for
the new task it is secure because it was created by the fs and thus got a
task ID reference inserted automatically (otherwise consider what could
happen if the filesystem dies just after starting the new task and before
the new tasks sends the initial startup message - it might send it to the
wrong task errorneously without being able to know that).

In the variant I suggested, where the task was originally created by the old
task, and passed to the filesystem, I didn't cover this point yet.  I guess
that the filesystem server must insert a reference to itself into the new
task before it starts it up, so the new task can be sure to know about it
reliably when the filesystem dies just after it started the new task.
The filesystem can be trusted to do that, as it is already trusted with the
task anyway, so this is not a scurity but a robustness issue.  (Counter
check: If the filesystem were malicious, it could just forward the startup
message to any other thread anyway).
 
> 5. File server passes the handle on back to the old task (and this is
>    the end of the file_exec rpc).

You have a point here, as this would indeed asure that new task doesn't die
intermediately.  However, passing back handles that don't refere to the
server itself is always problematic.  Both the new task and the old task can
indefinitely stall such a handle passing.  New task can stall the server
thread anyway, as the fs server will block on the startup message.  But the
user should not be able to do that.

This is what I called a "complicated upcall" above.  The server can only
move the handle to the old task by sending it a message and waiting until
the old task received and accepted the handle (otherwise there is a race
against the server releasing the handle).  This is what I want to avoid by
creating the task in the old task and passing it down.

> 6. The returned handle is used to perform a second startup exchange
>    between the old and the new task. This includes the PID of the old
>    task.
> 
> 7. The new task registers with proc, and uses the auth handles from
>    step 3 to change it's persona. With the proc registration, proc
>    also changes the accounting id for the new task to its pid.

Except for the accounting ID thingie (see above), this is ok.
 
> This seems to cover all related handles. The actual loading of the new
> code to execute is omitted, hopefully it can be done in the same way
> for ordinary and suid exec, with and without interpreters.

This seems to be the case in the current implementation.

> For
> non-secure exec, the new task gets a file handle from the old task,
> for secure exec (including executable and non-readable case), the new
> task must get it from the file system.

Roughly that way, yes.

The main issue I don't consider to be resolved is:
Should the user create the task and pass it to the fs, which revokes all
task handles except its own?  Or should the filesystem create the task?
The answer depends on what can be done about the task ID references (so
that no involved party can die and be replaced without the other's noticing
it), and what the accounting ID should look like.

The issue of how exactly the filesystem starts the new task seems minor to
me:  The fs server has complete control over the task, so it can always
achieve a robust solution.  For example, it could arrange it so that there
is a small loader program loaded into the task that arranges for the startup
data to arrive (without blocking the fs server) and made available to the
real program.  (Although I would hope for a simpler solution in a first
version that builds on cooperative models!).

I think you made good points about having new thread care about the pid
reassign.  How exactly this works, and when the old task dies (or must die),
and when other procish things are done and how is more of an implementation
detail.  As this happens after the new task completed its startup, I don't
care much about it.

I expect such an implementation to be faster and more robust than the
current exec server model, and, boy!, it will be a hell lot of easier to
debug.  I still remember debugging interpreter scripts in fakeroot, running
four gdbs in parallel (on the program, on fakeroot, on the real fs and on
exec).

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]