Re: In-kernel Authentication Tokens (PAGs)

From: Kyle Moffett
Date: Wed Jun 23 2004 - 16:04:19 EST


On Jun 23, 2004, at 08:29, David Howells wrote:
Kyle Moffett <mrmacman_g4@xxxxxxx> wrote:
You are referring to the attachment point from the UID to key-ring or
process to key-ring. I was referring to your method of telling the key-ring
what is attached to it, though I could have misread your code. Of course a
task_struct should have a key-ring pointer, but the key-ring shouldn't need
to know what points to it, just how many things point to it (ref count).

A keyring doesn't know what points to it, only the keys that it holds. The key
part of the keyring keeps track of the refcount.

A keyring does have a name, though, but it is arbitrary and otherwise ignored.

Ok, that makes sense, I just confused myself when I read your code. Could we
just use numbers? Possibly a port of the PID allocator would make that easier.

I see, we're going about this different ways. For me, the ideal search path
within a single key-ring: keyring, keyring->parent, keyring->parent->parent,
etc.
So you're thinking of a credential stack? That gets tricky when su is thrown
into the mix. Not that I'm saying my method is precisely simple then either.

Actually, you don't need the concept of a parent at all. If the process had a
current credential TOS pointer, you could push another keyring by adding the
TOS pointer as a child of the new keyring, and then redirecting the TOS
pointer. Basically, the old TOS becomes a child of the new TOS.

I've suggested a stack before, but it got rejected for various reasons.

We've been looking at this from different perspectives. My search system was
to have a special "parent" that is searched if a key cannot be found in the
current key-ring. Your search systeam appears to be to have a bunch of
"children" that are just an extra collection of more keys that are searched after
the key-ring.

That way we wouldn't even need a "session" key-ring in the kernel, a PAM
module could join processes to the appropriate key-ring when you login.
That way if I login several times on different console virtual terminals it
can share a key-ring across all of them, but not when I login remotely.

True. You would still have a "session" keyring, but it would be entirely
defined and governed by userspace (PAM) as to what it meant.

I sort of like that idea. The kernel could still pin keyrings for groups and
users, and PAM could bolt them together, so upon login PAM could create:

TOS
|
+--> Session keyring
|
+--> UID keyring
+--> GID keyring
+--> Supplementary Group keyring
+--> Supplementary Group keyring
+--> Supplementary Group keyring

And then a process or a thread that wanted its own private keys could stack a
new ring:

TOS
|
+--> Thread keyring
|
+--> Process keyring
|
+--> Session keyring

I really like that idea, but perhaps it could be made more extendable. Maybe
we could use a system like this:

Searching a key-ring involves searching its keys, then searching its children,
(Child order is undefined). When a task begins a search operation it searches
the following key-rings in this order:

Thread
Process
User
Primary Group
Secondary Group(s) (Undefined order)

The recommended way to join a process to a session is to change its process
key-ring to something like the following:
Process
|
+---New empty process key-ring
|
+---"Session" key-ring

However, you have a number of problems to contend with:

(*) How do you handle setuid() and co?

By default the init process receives a NULL key-ring (No key-ring at all). This
means that new processes spawned by init receive a NULL key-ring. These
are NULL key-ring pointers, not empty key-rings, so no data/keys are shared.
If a process tries to create keys in a NULL key-ring, it will fail. Then setuid(),
etc. merely change the uid, etc. If a daemon is explicitly given a keyring at
startup it will retain that keyring. This preserves maximum compatibility even
though there are no changes to libc.

(*) How do you handle setgid() and co?

The same way as setuid().

(*) How do you handle setgroups()?

The same way as setgid().

(*) How do you handle S_SUID?

This last could be handled in three ways: stack a new credential on the
front; have a second TOS pointer (similar to UIDs); or start a new stack.

If having a second TOS pointer, you could have setresuid() clear it if
setting all UIDs to non-zero.

(*) There needs to be a limit on recursion.

As long as we're careful to do all key-ring operations within an interruptible task
context, and only use a locking iterative search, we don't need to care about
tree depth. If the user creates too deep of a child structure, it just gets credited to
their process time and user limits. Iterative searches eliminate the stack usage
problems and make it simple to fit in a 4k stack limit.

Let's allow user programs to *request* (Could be overridden) that certain
keys be swappable, and we could always allow them to be in highmem, as long
as we can ensure that certain keys won't be swapped. There are advantages
to not allowing keys to be swapped.

I'm not sure making keys swappable is necessarily easy.

So in the initial implementation of the key-ring system all requests for swappable
keys would be overridden to be not swappable. After all, it's only a *request* :-D

Put a counter in "struct user".

Possibly also per-process or per-thread limits. Maybe even per-group limits, if we
want to go all the way. Those are relatively simple, though.

Are the serial numbers unique within a key-ring or within the entire
subsystem?
The latter.
That makes it much easier to move keys around between key-rings, I guess.

Are the types numbers? That would seem simpler and allow differing
user-space and kernel-space key-type allocation. Then it would be:
type: KEYTYPE_KRB5 (1042 or some such user-space allocated number)
desc: "krbtgt/MY.REALM@xxxxxxxx"

No. The types are names. I suppose they could be made numeric too, but I don't
think there's a need for that. I could just decree that all userspace type
names begin with a '+' or something.

I suppose that makes sense. I think at one point I had a technical reason for why
types should be numbers but it seems to have gone away. Oh well :-)

Some of this could be done by link and rename.
Yeah, but carefully.

Actually, symlink() would probably be better. Though Al Viro might kill me for
abusing it:-)

We want to be careful to give processes a way to prevent race conditions
when accessing/modifying key-rings.

Let's try not to bend the VFS layer too far. Just add another syscall or
prctl() for that.

Yeah. Generally we want to give them a file or directory handle instead of a
key-ring ID. That way we have a simple way to detect when they're done
using it.

Perhaps it'd be better to make each key a directory, whether or not it's a
keyring:

/proc/keys/
types
keys/
<keyID>/
type
state
description
payload
<keyringID>/
type
state
description
<keyID> => ../<keyID> [symlink]

I like this idea. It's a simple shallow directory tree.

We can also store sub-key-rings that way. Here "unlink()" of a directory
could be permitted.

I don't think you can unlink() a directory, and rmdir() might not work if it's
got contents.

Yes, but unlink would only be needed on the symlinks, which is what we
need. The keys and key-rings themselves would go away when all
references to them have been destroyed.

With some special keyIDs:

0xABCD0001 - This thread's keyring
0xABCD0002 - This process's keyring
0xABCD0003 - This session's keyring
0xABCD0004 - This UID's keyring
0xABCD0005 - This GID's keyring

Why not just have separate keyctl calls to set thread, process, UID, and GID
keyrings for specific threads/processes/UIDs/GIDs. That way a process with
the appropriate capabilities can manipulate keys as needed. It also frees
us from the need to worry about not allocating those particular IDs.

Yeah. We ought to have equivalent IOCTLs so that mostly atomic updates can
be done to key-rings, possibly even setting up a mandatory flock() for key
and key-ring file-handles. Opening a file-handle would be enough to make
sure it doesn't go away, but flocking it would protect against other kinds
of operations.

We don't want to add ioctls if we can avoid it... And I don't think you want
to try mixing flock() in.

What you're suggesting makes filesystem key searching tricky... what happens
when it is running in softirq context and encounters a locked keyring?

Is there anything that needs to run in softirq context that should be accessing
key-rings there? Perhaps one condition on key-ring access would be to
require that it be done from interruptible task context. We could re-implement
the flock operation for our particular key filesystem to be a mandatory key lock.
That would prevent race conditions in priv'ed processes manipulating the
key-rings by allowing atomic modifications on a large scale.

hard-link or soft-link to what? Keyrings are directories on another
filesystem, and we can only assume that it's mounted on /proc/keys. Besides,
you can't hard-link directories.

Ahh, sorry, I was thinking and got lost. Nevermind :-D
Perhaps we should add a few /proc/<pid>/keyring/{thread,process,...} "files"
to allow the sysadmin to view what key-rings are currently used by a particular
process.

Cheers,
Kyle Moffett

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/