Let me work thru a typical capability transaction ...
We consider an application component as it invokes another component within a capability system. We then consider what might be necessary in a Unix system to achieve the same degree of isolation of accountability of these two components that was achieved in the capability system.

Steps that we will suggest are seldom thought necessary in classic (Unix) systems, but perhaps this is largely due to their substantial cost there.

Component B invokes C which runs in another address space. Unix provides several ways of doing this: pipes, exec, and some Unixes have custom means: Solaris has its undocumented “doors” and Linux has “clone”.

B has created about a megabyte of data, X, that C must read to produce about a kilobyte of data to return to B. C will invoke other components unknown to B in this task. The convention between B and C is that B will dispose of the storage holding the response when it is done with it.

In either system we must decide what steps are necessary for B to have access to C. I shall assume in this case that the invocation of C is not to interact with others who invoke C; in effect C is free of side-effects.

A capability OS scenario

B invokes a segment creator for a segment to hold the data X. A segment capability, S, is returned to B in the response. It chooses an address at which to access that segment and maps the new segment there. It creates the data incrementally, storing into the mapped segment. It makes all sorts of references to other facilities during this time.

It comes time to invoke C. B invokes the segment capability S to obtain a read-only capability, R, to the same segment. B “calls” a capability to C passing R. C installs the received capability in its own address space in order to read the data. C creates a segment for the response and installs that segment at another address. C finds from the data that yet another consultant is required for the job and passes R to that sub-sub contractor. The response in this case is an integer which is returned by value. C resumes its work and produces the data required by B. C returns to B passing the segment with the answer. B maps the segment with the response and uses the answer. B then invokes the returned segment to delete it. It may also invoke S to delete it if it is done with S too.

End of capability OS scenario.

A Unix Scenario

B creates a new file X. A directory must be chosen in which to register a name for X. This must be a directory in which B may make new entries and from which C may open files. B would not ordinarily have a directory exclusively of its own, but we shall assume here that the code that launched B also created a directory exclusively for B. B will write the data into X either by classic file writes or by mmap; the choice is private to B. Of course B and C might agree to run concurrently and send the data thru a pipe. Capability systems have similar tricks and that is another interesting scenario which we will not pursue here. We assume that the data format for S is not naturally produced sequentially. When it comes time to invoke C, we shall assume that B will perform the exec system calls “fork” and the child process will perform “execve”. This is the only easy way I know to separate planning of storage layout between B and C. Also B has not finished and cannot itself perform the execev which would annihilate its storage.

I will assume that C is accessible to B in the form of an executable file for which B has execute and read permission. (I don’t know why B needs read permission. I should think that execute permission would suffice. The kernel needs to read the file but B doesn’t. Furthermore this fact seems not to be conveyed in the specs. Try ls -lt /usr/bin on just about any Unix system.)

Program C will now commence with (almost) all of C’s authority and some of its own if the directory entry via which B accessed C included the set-user-ID mode bit. B can arrange that an agreed upon file descriptor already accesses the file with the data that B prepared for C. This way there is no need to arrange for permissions or ACL mechanisms for C to access the data. mmap will work for C independently of whether B produced the file that way. The data that C needs to return to B is not as easily returned. The simplest way that I see of for B to create an additional file into which C can deposit the result. B can issue the wait system call until C has finished its work.

End of Unix Scenario.

A comparison

In the simple scenario above we have found adequate ways to perform the required functions while allowing the contractor and subcontractor maximum independence on how they work. In Unix the subcontractor C runs with too much authority, with enough to annihilate all of B’s work products. B can perform the sys-call equivalent of rm -rf *. C can bypass any proprietary steps taken by C by copying C and executing the copy. Am I being unfair to Unix?