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: Thu, 22 May 2003 14:49:13 +0200
User-agent: Mutt/1.5.3i

On Thu, May 22, 2003 at 10:13:24AM +0200, Niels Möller wrote:
> The main reason I don't want the user task to create the task, is that
> then the fileserver has to ensure that the user task didn't leave any
> code or boobytraps in it. That seems a little hairy.

In general, this can be accomplished with appropriate task server support.
For example, the task server could internally create a new address space
(thus a new task) as part of the task_revoke call.  In fact, a task server
could not create any address space at all at the time of the task_create,
but only fill in the accounting ID and allocate a task ID and create a
handle for the task.  Then task_revoke would not even get a performance hit.
The address space could be conveniently created with the first thread (as it
is done in L4 anyway.  Only when the last thread is destroyed the task
server must make sure to keep the address space around).

The task server could signal to the filesystem that the object it received
is not a "virgin task" and thus can not be safely used (a virgin task would
be one that is not in the state right after task_create, whatever that state
is.  Probably the only requirement is that no thread has been created in
it).

This assumes real handles instead just task IDs to be used for
authentication (otherwise the fs server could not gain access to the task
created by old task).

Another issue is who is responsible for destroying the task.  Before the
filesystem does the task_revoke, it is not responsible for it.  Afterwards
it is.  So this makes it a bit more complex than having the fs server
creating the task.  I would count that argument against my idea.  But that
is not critical, I think.

> And then it
> requires a task port abstraction (the task server gets simpler if we
> can get by without that: I'd prefer that the task server not know
> about object handles, because it implements task references which is a
> building block for handles).

Mmh, I am not sure we can do without a task port abstraction.  Well, maybe
if you use the accounting ID for basic authentication and proc server special
privileges for global authentication.  But that just sounds like having a
special solution for something that doesn't need to be special.  Of course,
no references need to be taken for handles to the task server (although
there'd be no harm if someone would try to do that, or even if the task
server would implement that).  In fact, even task ID references are special
in that they are not normal object handles as they should be (which everyone
could request).  The reason that I suggest them not to be normal objects is
purely an optimization: There is a need for every task to get such
references to every other task it wants to communicate with.  So there will
be a lot of such objects.  This can be implemented efficiently by just
having a bit matrix.  Two pages (8kb) could record all task references for
256 tasks.  For 512 tasks, you still would only need 8 pages (32kB) [1024
tasks -> 32 pages (128kB), and the insane maximum 2^14 = 16384 tasks could
be stored in convenient 32 MB].  If you would use normal records in task
structures, for 512 tasks this could be 2MB of mostly useless junk :)
The reference counting must then be done by the user itself as part of the
handle library.  Now one could say that the matrix carries all possible
information, so it is a worst case scenario.  In reality, only parts of the
tasks would communicate at a time.  So if you do dynamic allocation in task
structures, and every task of the T tasks only communicates with a fixed
number N of other tasks, you would more get a behaviour like T * N
references to record.  If T = 512, N = 16, and 4/8 bytes per record that
would be 32/54 kb.  However, if the worst case in the one scenario is just
as good or better than the average case in the other scenario, I prefer the
worst case I guess.

This optimization is only possible because the task server itself does not
rely on such tricks to secure IPC: It always knows exactly which tasks are
in what state, and which have died and which task IDs are reused, etc.

> If it is important to get the right accounting from the start, I think
> it's cleaner to pass the old tasks pid handle to the fs. Then the
> filesystem can cooperate with task and proc to get it right from the
> start (task needs a privileged call that creates a task with a given
> accounting_id, and proc needs a call that takes a pid and invokes that
> call. Task considers proc (but no other task) as privileged). But that
> also seems pretty complex.

This seems to hairy to me, too.  It also gives the fs privileges it doesn't
really need.

> > > 5. File server passes the handle on back to the old task (and this is
> > >    the end of the file_exec rpc).
> 
> > 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.
> 
> I think I see the problem. Perhaps one can tweak this, as the fs has
> full control over the new task. I don't remember the exact protocol
> for handle transfer, but what is needed is:
> 
>  * The new task should get a reference to the old task, and record
>    that the task is allowed to communicate with it. (Involves file
>    system, new task and the task server, so I think this should be
>    easy).

Yes, that one is ok.
 
>  * The old task should get the thread id of the new task, and a
>    reference to it. This is the hard part, it seems.

Right, this is the main problem that pushed me in the direction of the
pre-file_exec-task-creation which now has become a
pre-file_exec-task-ID-reference-taking.  The main idea behind this is that
you don't need to pass from fs to old task what already _is_ in the old
task.

Here the moving handles protocol again:

Moving Object Handles
---------------------

This is how a client A can give B an object handle for an object
provided by server S.

Over the whole time, S keeps a reference to A's task ID, just because
A is a client of S (see above).

Over the whole time, B keeps a reference to A's task ID, not only
because A is a client of B, but also to ensure that it gets the handle
that was granted by A and not by any other task.

Over the whole time, A keeps a reference to S's task ID, not just
because A is a client of S, but also to ensure that B contacts the
same server S as A did.

Over the whole time, A keeps a reference to B's task ID, not just
because A is a client of B, but also to ensure that the same B that A
granted the object handle receives it from S.
Here is the synchronous version (A blocks on B):

1. A requests a transaction record T for B from S.  T is kept in S
along with A's other references to the object.  T is removed either
when A requests it or if A dies.  T is only valid for B.

2. A sends the server thread ID S and the transaction record T to B.

3. B sends the task ID of A and the transaction record to S.

4. S takes a reference to B's task ID and replies to B's message (from
   step 3) with the object ID.

5. B takes a reference to S's task ID and replies to A's message (from
   step 2) signalling success.  (If this fails, it should destroy the
   handle it got from S, and releases its reference to S's task ID, as
   A probably didn't have a chance to protect S's task ID integrity).

6. A sends the server the transaction record T to get it destroyed.

7. S destroys the transaction record.

8. A can now safely release its own handle to the object, as B has
   established its own, independent connection.  A can also release
   its connection to B.

Note that only A can guarantee that really S and B establish a
connection, and not S with some other rogue task B' (which grapped the
task ID of B) or some rogue task S' with B (which grapped the server's
task ID).  But as A provided S with B and B with S, it is the only one
who needs to care anyway.

Note that in this protocol S does not need to send (notification)
messages to either A nor B.

Note that if any of A, B and S fails to follow the protocol, they can
not do additional harm to any of the others.  Mutual trust is not
required.

The cost of this protocol is, beside the message from A to B (plus
reply), and the two messages to the task server (plus reply), three
messages (and their reply) to the server S.

If a message contains several handles, all handles could be processed
at once by allowing array forms of the server RPCs.

Here is the asynchronous version (A doesn't block on B):

1. - 4. As above.

5. B takes a reference to S's task ID.

6. As above.  A special RPC or flag is used to let the RPC block in
   the server until B completed the transaction or it receives a task
   death notification for B (or S of course).

7. - 8. As above.

Note that A should always wait for B to complete (either by waiting
for B's reply, or by blocking in the server), because otherwise, if A
drops the connection to S prematurely, S might drop the transaction
record before B can accept it, or S might send to a rogue B' (which
stole the task ID from B).

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]