Comment by jdougan

2 days ago

If you go through old CS OS texts on the matter, they really didn't have the same understanding of capabilities then as the later object-capabilities (ocap) model would introduce. Typically they would show an access control matrix, note that acls were rows and capabilities columns and note that they are duals of one another. They're the same, acls are easier to manage, done.

OP is arguably the first paper that introduces ocaps. Some of the issues are discussed in "Capability Myths Demolished" https://papers.agoric.com/assets/pdf/papers/capability-myths...

They mention a compiler having access to a file called BILL for storing billing information and if you specify that it is the file for debugging then it is overwritten by the debugging information. While an appropriate kind of capability system (such as proxy capabilities, or object-capabilities described in that article which is very similar) can help, locking the file might also help (if it is locked for billing first before any files specified by the user are locked); then the compiler will complain that the file specified as the debugging output file cannot be written because it is locked (even though the compiler is the one that locked that file). A capability system is better, although it would be possible to do both, since locks (and transactions as well) are also helpful for other purposes.

I’m not going to argue against much of the content of this paper, but it should be pointed out that their argument in the middle section against the “confinement myth” seems pretty bogus. They say that you can isolate the capability read/write resource from the data read/write resource, but… this makes absolutely no sense. Bits are bits. If you assume some out-of-band isolation of capability distribution then you’ve changed the game, but even that isn’t enough for me to believe that isolation is possible.

  • Early thinking was in terms of capability handles. As with file descriptors, the handle is only meaningful when passed across a protection boundary to something which can check if the handle is valid.

    Later, there were encrypted capabilities, which are signed data, like TLS certs. These get kind of bulky. And hardware support, in a few machines.

    • alternate futures where the 33bit versions of the i960 became the processor family of choice.

  • Consider two processes on a *nix system, and for the sake of argument let's say they're sufficiently isolated from each other as to have only one communications channel between them. If that communications channel is a unix domain socket, one process can send a file descriptor (effectively a capability) to the other over the socket. Each process has a file descriptor table in the kernel whose integer keys are only meaningful to that process in particular, and the kernel provides a mechanism to transmit file descriptors across a socket. The kernel mediates in this case.

    If the communications channel is not a unix domain socket and is instead something like a TCP connection, you don't have this option available to you.

    You aren't always just sending bits from one process to another!

    • No, you’re using the same sleight of hand as the paper.

      Boebert’s objection is about whether Alice can transmit unauthorized authority to Bob across a security boundary that’s supposed to prevent that flow. Your SCM_RIGHTS example is a case where the kernel is deliberately providing a sanctioned channel for authority transfer, with the kernel’s blessing, between two processes that the kernel does not consider to be on opposite sides of a mandatory access control boundary. Unix has no (*)-property. There is no “high” and “low” in the Bell-LaPadula sense on a standard Unix system. So of course the kernel mediates the transfer cleanly; it’s not enforcing any policy that would be violated by the transfer.

      The moment you try to extend this to the actual case under dispute—Alice is “high,” Bob is “low,” and the security policy says high-to-low information flow is forbidden—then if the kernel refuses to deliver the fd across the boundary, the security property was enforced by the separate MAC layer, not the capability mechanism.

      The conflation which is endemic in this whole debate is between “capabilities as a kernel-mediated authority mechanism” and “capabilities as a property that holds across all observable behavior of the system.” Unix file descriptors are the former. Boebert’s objection is about the latter.

      2 replies →

  • That argument assumes that the delegation of a capability to another process must happen through a path of interprocess communication that can be established only by the operating system, if the processes that want to communicate have the capabilites for this.

    I have not studied to see how the existing capability-based operating systems solve this problem, because it seems that this is not a simple solution. If the capabilities are very fine-grained, to make certain that IPC really cannot happen, that might be cumbersome to use, while coarse-grained capabilities could be circumvented. To really prevent IPC without appropriate capabilities, a lot of the convenient features of a UNIX-like system must be forbidden, like the existence of files that can be read by any user, or directories like /tmp , where anyone can write a file.

    • > If the capabilities are very fine-grained, to make certain that IPC really cannot happen, that might be cumbersome to use, while coarse-grained capabilities could be circumvented.

      In SeL4 it’s kinda like this: A capability is an opaque handle you can invoke to RPC into some other process or into the kernel. There’s no worry about how fine grained capabilities are because there’s no global table of permission bits or anything like that. Processes can invent capabilities whenever they want. Because caps just let other processes call your code, you can programmatically make them do anything.

      Suppose I want to give a process read only access to a file I have RW access to. The OS doesn’t need a special “read only capability” type. It doesn’t need to. Instead, my process just makes capabilites for whatever I actually want on the fly. In this case, I just make a new capability. When it’s invoked I see the associated request, if the caller is making a read request, I proxy that request to the file handle I have. (Also another cap). And if it’s a write request, I can reject it. Voila!

      This is how you can write the filesystem and drivers in userland. One process can be in charge of the block devices. That process creates some caps for reading and writing raw bytes to disk. It passes the “client side” of that cap to a filesystem process, which can produce its own file handle caps for interacting with directories and files, which can be passed to userland processes in turn. Its capabilities all the way down.

      9 replies →

    • Covert channels are a thing. Shared access to resources always opens the possibility of covert information passing through e.g. modulation of resource usage. This isn’t even out-of-band, it’s just a hard fact that a shared resource always creates a potential covert channel (source: Lampson 1972, A Note on the Confinement Problem).

      1 reply →