Thoughts on a token-based security system

David Madore (madore@clipper.ens.fr)
Sun, 10 Jan 1999 02:00:02 +0100 (MET)


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/