Messages in this thread | | | From | (David Madore) | Date | Sun, 10 Jan 1999 02:00:02 +0100 (MET) | Subject | Thoughts on a token-based security system |
| |
Hi everybody,
I have some ideas to suggest. They are not really made to be implemented, though I might get around to having a try at that if I find I have some time to spare (something pretty unlikely), or if somebody thinks those ideas are wonderful (something even more unlikely :-). I mostly want to have the experts' opinion on whether these ideas are interesting, whether they are useful, whether they are feasible, whether they have been thought of before (the contrary would surprise me greatly), and whether there is already a project on similar lines. I am on no account suggesting that all this should go in the standard Linux kernel.
UNIX is terribly bad in its organization of security. I don't mean this as a troll. Nor do I mean that UNIX can't be very secure. And in any case my idea does not aim to make it more secure (let alone fulfill the Orange Book Class (A1) requirements), but to make the security mechanisms more flexible and more versatile.
Let me explain. The basic security hierarchization scheme is the existence of UID's. But those are pretty limited. UID 0 has all the power, and the rights of the other UID's have pairwise the same intersection (i.e., on the UID level, if users X and Y can both do something then everyone can). This is terribly restrictive. It is a little like having a directory structure that just has two levels: the root, from which one can go everywhere, and one directory per user (I think ITS was like that - no offense meant). Of course, there are workarounds. GID's are one. SUID programs (which might read a config file to see whether such or such a user should be allowed to do such or such a thing) are another. But basically we are stuck in a Turing tar-pit and we have to invent unlikely ladders to escape. (In the directory structure analogy, even in a system that has the limitation I mentioned, it is always possible to manipulate tarballs and emulate directories like that, but it ain't nohow natural.)
Practically, if I want to run some program that I don't trust, there's not much I can do to limit its privileges without help from my sysadmin. Well, I might su to nobody if the system is configured to let me do so (I think anyone should be allowed to su to nobody). But I can't switch to a sub-user that has only a subset of my privileges without having *only* the privileges which everyone (``nobody''!) has. (In the directory structure analogy, I can't create a subdirectory of my directory.)
So what do I suggest? Basically, something which exists, at least to some extent, in VMS and in the GNU Hurd. Security tokens.
Basically, a security token is just a number, an identifier. It represents a specific permission or privilege. To each running process is associated a set of tokens which the process possesses. Some tokens allow the process to actually *do* something as far as the OS is concerned; say, split up the ``root'' privileges in a lot of sub-privileges for doing various things (so that a process won't have to be root unless it really needs to) - but that isn't the most important point. The other tokens (most tokens) don't have any special meaning as far as the OS is concerned. They are used for inter-process communication and authentication.
If during a client/server communication the server needs to authenticate the client, things will proceed as follows: they first tell each other their PID. Then the server asks the client to authenticate itself. The client has the appropriate token and asks the operating system to permit the server (specified by its PID) to see it. The client tells the server that it has shown its token to the OS. The server asks the OS whether the client (specified by its PID) has indeed shown the token in question. The OS answers that it has (or that it hasn't - note however that the OS doesn't tell the server that the client has or doesn't have the token, but rather whether it has or hasn't *shown* it). The client is then successfully authenticated and communication can proceed. (Note that all this scenario takes place through a pipe, UNIX socket, or, if implemented, door - this can't work through a network unless some other mechanism is found to play the ``arbiter'' role of the operating system.)
So there are six syscalls associated with security tokens. [A]Request a new token. That is, the OS creates a new, meaningless, token, and gives it to the requesting process. It is up to that process to give some meaning to the token, by offering a service that will depend upon possession of the token. [B]Show a token to a given process. That is, allow the process in question to verify that the calling process really has the token (it is otherwise not possible to know about which processes possess which tokens - unless of course one has a special OS-meaningful token which allows to do that). [C]Verify that a process has shown a token. [D]Destroy one of one's tokens (useful before calling exec()). [E]Give (copy) a token to another process. (Morally, I consider that any right is transferable: if I have the right to do something, I can always give you that right by telling you that I am willing to exercise that right on your behalf. So we might as well make all tokens completely transferable.) And of course [F]Ask the OS which tokens I have.
(If we want to do things really correctly, we shouldn't use PID's for token operations with other processes, but rather some unique process identifier, so that PID reuse will not have the consequence that some process can impersonate another. I'm not sure how important that is.)
There are some policy decisions to be made about the behavior of tokens upon process creation. I think it is reasonable for the child process to inherit all the tokens that the parent had at the date of birth (knowing of course that tokens can be selectively destroyed later). What if the parent acquires tokens later on? Should the child get them? Clearly not. But if the *child* gets some new tokens? Should the parent get them automatically? I don't think so, but provision should be made for this to be possible. Perhaps any fork() should give the parent a ``child control'' token, allowing it to request a copy of any token the child has. Also, that ``child control'' token would allow the parent to send signals to the child (or, by transitivity, to grandchildren). (The reverse behavior can be obtained when explicitly required, if the parent process passes its own self-control token to the child. The main question is what kind of token passing should be automated so that too many programs won't have to be rewritten - but there's no doubt that anything *could* be done in any case.)
Of course, the really hard part is when the filesystem enters the game. Strictly speaking we could omit that part completely: if a process needs to access files that can always be emulated by a demon - Turing tar-pit again - but since Linux is not Hurd this is not a convenient way of doing things. To every file should be associated at least a right-to-read (a token that a process must possess if it wishes to read the file) and a right-to-write. (Any kind of more complicated rights, such as ``you may read this file if you have this token and that one, or if you have that third token'', can always be constructed by creating an ad hoc token for the combination, and using a token exchanger server that will give you the ad hoc token in exchange for showing it the various required tokens to access the file. Finding a way to do this transparently would be wonderful, of course - maybe tokens can be explicitly created with an equivalence statement and the exchange done automatically by the kernel when the token is to be checked.) Instead of a umask we have a creation requirement default (such as my UID-token for the right-to-write and the trivial token for the right-to-read). Plus, we want some STID (Set Token ID) executable files. Big question: can all this be done without extensively rewriting the VFS layer? Can it be done in an ext2-ascending-compatible way? Can we avoid breaking all programs like tar? Another question is how exactly the ``permanent'' tokens associated with the filesystem should relate to the ``volatile'' tokens described previously: how are permanent tokens created, acquired, assigned and numbered? (I can think of several possible answers to that, I just don't know which is best.)
All this being said, let me mention a few of the goodies that tokens would permit, to reward you for reading this far :-). First, there is the limited-right execution: I can launch a shell, explicitly destroy a few of its tokens, and run an insecure program from that limited-rights subshell (so perhaps it can't modify my files, perhaps it can't even read them, or perhaps it's limited to a specific subsystem of files that is accessed with a special lower-level security token created precisely for that purpose). A contrario, there is the special-rights execution: I might have a set of files that I want readable only upon presentation of a special token, so that normally I can't do it (and if I forget to log out or some such thing, people can't immediately access these files). To get that token I have to ask a token server and give it a special password in exchange (the token server might be an STID program that I created when I created the token and assigned it to the files, before I deleted the token from my normal privileges). Then there is the easy group creation feature: if a friend and I want to start a project together, we don't have to ask the sysadmin for a group (and we know groups sometimes make things difficult - they are non-nestable, you never know what umask to use, and all that), we just create a new token which we both have (one of us creates it and gives it to the other), and there is no problem about having this token _plus_ all the other tokens I might have. I can also easily make a file of mine accessible to just a few of my friends, or to people having a certain set of tokens, by creating a special token for the file, and informing the OS (or perhaps some special token server) that it should allow conversion from certain fixed conditions to the token in question. Finally, a lot of SUID root programs could be given more restrictive permissions if specific syscalls require specific tokens rather than just the ``root'' token (which would of course still exist - as the Ultimate Token That Can Generate All Others - but it just wouldn't be as frequent).
The problem is not so much whether all that I've described can be implemented - it certainly can, and in fact it's not all that hard - but whether it can be implemented in a UNIX-compatible fashion. I guess UID's and GID's should constitute particular kinds of tokens, but it's hard to imagine exactly how everything could be made to work in a perfectly transparent and upward-compatible way.
<troll> How come is it that after 30 years of existence UNIX still hasn't come to include such obvious and obviously necessary features as those that I've described? How come is it that the best OS in existence is 30 years behind its time? (And the most popular one is 2000 years behind its time - but that's beside the point.) </troll>
Anyway, I'd like to know what you all have to comment on this, whether on the theoretical side, or on the question of how that could/might/should be implemented in the Linux kernel. (I'm pretty ignorant, but I surmise that the basic mechanisms would be rather easy to set up, whereas the filesystem stuff is *really* hard.)
Please CC all replies to me personally <david.madore@ens.fr> (I read this ml only occasionally, through a mail-to-news gateway).
-- David A. Madore (david.madore@ens.fr, http://www.eleves.ens.fr:8080/home/madore/)
- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/
| |