Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S262827AbTIEOVG (ORCPT ); Fri, 5 Sep 2003 10:21:06 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S262866AbTIEOVF (ORCPT ); Fri, 5 Sep 2003 10:21:05 -0400 Received: from pub237.cambridge.redhat.com ([213.86.99.237]:44485 "EHLO warthog.warthog") by vger.kernel.org with ESMTP id S262827AbTIEOTy (ORCPT ); Fri, 5 Sep 2003 10:19:54 -0400 From: David Howells To: torvalds@osdl.org cc: linux-kernel@vger.kernel.org Subject: [PATCH] authentication / encryption key retention User-Agent: EMH/1.14.1 SEMI/1.14.4 (Hosorogi) FLIM/1.14.4 (=?ISO-8859-4?Q?Kashiharajing=FE-mae?=) APEL/10.4 Emacs/21.2 (i386-redhat-linux-gnu) MULE/5.0 (SAKAKI) MIME-Version: 1.0 (generated by SEMI 1.14.4 - "Hosorogi") Content-Type: text/plain; charset=US-ASCII Date: Fri, 05 Sep 2003 15:19:29 +0100 Message-ID: <6999.1062771569@redhat.com> Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 45665 Lines: 1721 Hi Linus, Here's a patch to the kernel that can provide authentication token and encryption key retention at various levels. Could you have a look at it and see if it's along the lines of what you want? Note that it uses the prctl() syscall to propose keys to the kernel. One thing I think it needs is some way of limiting the number of extant keys a user is permitted to have. I can now use it to retain AFS keys obtained from Kerberos. David diff -uNr linux-2.6.0-test4/fs/exec.c linux-2.6.0-test4-keys/fs/exec.c --- linux-2.6.0-test4/fs/exec.c 2003-08-26 14:04:44.000000000 +0100 +++ linux-2.6.0-test4-keys/fs/exec.c 2003-08-27 15:00:11.000000000 +0100 @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -889,10 +890,15 @@ void compute_creds(struct linux_binprm *bprm) { + if (bprm->e_uid != current->uid) + suid_keys(current); + exec_keys(current); + task_lock(current); if (bprm->e_uid != current->uid || bprm->e_gid != current->gid) { current->mm->dumpable = 0; - + suid_keys(current); + if (must_not_trace_exec(current) || atomic_read(¤t->fs->count) > 1 || atomic_read(¤t->files->count) > 1 diff -uNr linux-2.6.0-test4/include/linux/key.h linux-2.6.0-test4-keys/include/linux/key.h --- linux-2.6.0-test4/include/linux/key.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test4-keys/include/linux/key.h 2003-09-01 14:24:18.000000000 +0100 @@ -0,0 +1,144 @@ +/* key.h: authentication token and access key management + * + * Copyright (C) 2003 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_KEY_H +#define _LINUX_KEY_H + +#include +#include +#include +#include +#include + +#ifdef __KERNEL__ +#ifdef CONFIG_KEYS + +#define KEY_DEBUGGING + +typedef int32_t key_serial_t; + +struct seq_file; + +struct key; +struct key_type; +struct keyring_list; +struct keyring_name; + +/*****************************************************************************/ +/* + * authentication token / access credential / keyring + * - types of key include: + * - keyrings + * - disk encryption IDs + * - Kerberos TGTs and tickets + */ +struct key { + atomic_t usage; + key_serial_t serial; /* key serial number */ + struct rb_node serial_node; + struct key_type *type; /* type of key */ + rwlock_t lock; /* examination vs change lock */ + struct rw_semaphore sem; /* change vs change sem */ + unsigned short datalen; /* payload data length */ + unsigned short flags; +#define KEY_FLAG_DEAD 0x00000001 /* set if key is deleted */ +#define KEY_FLAG_RETIRED 0x00000002 /* set if key is retired from service */ +#define KEY_FLAG_PUBLIC_KEYRING 0x00000004 /* set if publicly subscribable keyring */ + +#ifdef KEY_DEBUGGING + unsigned magic; +#define KEY_DEBUG_MAGIC 0x18273645u +#define KEY_DEBUG_MAGIC_X 0xf8e9dacbu +#endif + + union { + struct keyring_name *ringname; + void *data; + } description; + + union { + void *data; + struct keyring_list *subscriptions; + } payload; +}; + +struct key_type { + const char *name; /* name of type */ + struct list_head link; /* link in types list */ + + int (*init)(struct key *key, const char *desc, + size_t datalen, const char *data); + int (*update)(struct key *key, const char *desc, + size_t datalen, const char *data); + int (*match)(const struct key *key, const void *desc); + void (*clear)(struct key *key); + void (*describe)(const struct key *key, struct seq_file *p); +}; + +extern int register_key_type(struct key_type *ktype); +extern void unregister_key_type(struct key_type *ktype); + +extern void key_retire(struct key *key); +extern void key_put(struct key *key); + +extern int add_process_key(int specifier, + const char *type, + const char *description, + int plen, + const void *payload); + +extern int keyring_search(struct key *keyring, + const struct key_type *type, + const char *description, + struct key **_key); + +#define SEARCH_KEYRING_THREAD 0x0001 +#define SEARCH_KEYRING_PROCESS 0x0002 +#define SEARCH_KEYRING_SESSION 0x0004 +#define SEARCH_KEYRING_UID 0x0008 +#define SEARCH_KEYRING_GID 0x0010 +#define SEARCH_KEYRING_ALL 0x001f + +extern int search_process_keyrings(unsigned search_mask, + const struct key_type *type, + const char *description, + struct key **_key); + +extern struct key root_user_keyring; +extern int alloc_uid_keyring(struct user_struct *user); +extern int copy_keys(unsigned long clone_flags, struct task_struct *tsk); +extern void exit_keys(struct task_struct *tsk); +extern int suid_keys(struct task_struct *tsk); +extern int exec_keys(struct task_struct *tsk); +extern long get_process_keyring_ID(int specifier); +extern long clear_process_keyring(int specifier); +extern long new_session_keyring(void); +extern long add_user_key(int specifier, + char __user *type, + char __user *description, + void __user *payload); + +#else /* CONFIG_KEYS */ + +#define key_put(k) do { } while(0) +#define alloc_uid_keyring(u) 0 +#define copy_keys(f,t) 0 +#define exit_keys(t) do { } while(0) +#define suid_keys(t) 0 +#define exec_keys(t) 0 +#define get_process_keyring_ID(s) (-EINVAL) +#define clear_process_keyring(s) (-EINVAL) +#define new_session_keyring() (-EINVAL) +#define add_user_key(s,t,d,p) (-EINVAL) + +#endif /* CONFIG_KEYS */ +#endif /* __KERNEL__ */ +#endif /* _LINUX_KEY_H */ diff -uNr linux-2.6.0-test4/include/linux/prctl.h linux-2.6.0-test4-keys/include/linux/prctl.h --- linux-2.6.0-test4/include/linux/prctl.h 2003-08-26 14:04:47.000000000 +0100 +++ linux-2.6.0-test4-keys/include/linux/prctl.h 2003-08-28 09:55:22.000000000 +0100 @@ -43,5 +43,16 @@ # define PR_TIMING_TIMESTAMP 1 /* Accurate timestamp based process timing */ +/* Manage a process's keyrings */ +#define PR_SPEC_THREAD_KEYRING 0 /* - specifier for thread-specific keyring */ +#define PR_SPEC_PROCESS_KEYRING 1 /* - specifier for process-specific keyring */ +#define PR_SPEC_SESSION_KEYRING 2 /* - specifier for session-specific keyring */ +#define PR_SPEC_USER_KEYRING 3 /* - specifier for UID-specific keyring */ +#define PR_SPEC_GROUP_KEYRING 4 /* - specifier for GID-specific keyring */ + +#define PR_GET_KEYRING_ID 15 /* ask for specified keyring's ID */ +#define PR_CLEAR_KEYRING 16 /* clear contents of specified keyring */ +#define PR_NEW_SESSION_KEYRING 17 /* start a new session keyring */ +#define PR_ADD_NEW_KEY 18 /* add a key to specified keyring */ #endif /* _LINUX_PRCTL_H */ diff -uNr linux-2.6.0-test4/include/linux/sched.h linux-2.6.0-test4-keys/include/linux/sched.h --- linux-2.6.0-test4/include/linux/sched.h 2003-08-26 14:04:47.000000000 +0100 +++ linux-2.6.0-test4-keys/include/linux/sched.h 2003-08-29 10:42:20.000000000 +0100 @@ -290,6 +290,10 @@ atomic_t processes; /* How many processes does this user have? */ atomic_t files; /* How many open files does this user have? */ +#ifdef CONFIG_KEYS + struct key *keyring; /* UID specific keyring */ +#endif + /* Hash table maintenance information */ struct list_head uidhash_list; uid_t uid; @@ -402,6 +406,11 @@ kernel_cap_t cap_effective, cap_inheritable, cap_permitted; int keep_capabilities:1; struct user_struct *user; +#ifdef CONFIG_KEYS + struct key *session_keyring; /* keyring inherited over fork */ + struct key *process_keyring; /* keyring private to this process (CLONE_THREAD) */ + struct key *thread_keyring; /* keyring private to this thread */ +#endif /* limits */ struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; diff -uNr linux-2.6.0-test4/kernel/exit.c linux-2.6.0-test4-keys/kernel/exit.c --- linux-2.6.0-test4/kernel/exit.c 2003-08-26 13:27:21.000000000 +0100 +++ linux-2.6.0-test4-keys/kernel/exit.c 2003-08-27 13:09:01.000000000 +0100 @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -711,6 +712,7 @@ exit_namespace(tsk); exit_itimers(tsk); exit_thread(); + exit_keys(tsk); if (tsk->leader) disassociate_ctty(1); diff -uNr linux-2.6.0-test4/kernel/fork.c linux-2.6.0-test4-keys/kernel/fork.c --- linux-2.6.0-test4/kernel/fork.c 2003-08-26 14:04:48.000000000 +0100 +++ linux-2.6.0-test4-keys/kernel/fork.c 2003-08-27 13:08:54.000000000 +0100 @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -884,8 +885,10 @@ goto bad_fork_cleanup_sighand; if ((retval = copy_mm(clone_flags, p))) goto bad_fork_cleanup_signal; - if ((retval = copy_namespace(clone_flags, p))) + if ((retval = copy_keys(clone_flags, p))) goto bad_fork_cleanup_mm; + if ((retval = copy_namespace(clone_flags, p))) + goto bad_fork_cleanup_keys; retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_namespace; @@ -1021,6 +1024,8 @@ bad_fork_cleanup_namespace: exit_namespace(p); +bad_fork_cleanup_keys: + exit_keys(p); bad_fork_cleanup_mm: exit_mm(p); bad_fork_cleanup_signal: diff -uNr linux-2.6.0-test4/kernel/sys.c linux-2.6.0-test4-keys/kernel/sys.c --- linux-2.6.0-test4/kernel/sys.c 2003-08-26 14:04:48.000000000 +0100 +++ linux-2.6.0-test4-keys/kernel/sys.c 2003-08-28 10:43:08.000000000 +0100 @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -1417,6 +1418,21 @@ } current->keep_capabilities = arg2; break; + case PR_GET_KEYRING_ID: + error = get_process_keyring_ID(arg2); + break; + case PR_CLEAR_KEYRING: + error = clear_process_keyring(arg2); + break; + case PR_NEW_SESSION_KEYRING: + error = new_session_keyring(); + break; + case PR_ADD_NEW_KEY: + error = add_user_key(arg2, + (char __user *) arg3, + (char __user *) arg4, + (void __user *) arg5); + break; default: error = -EINVAL; break; diff -uNr linux-2.6.0-test4/kernel/user.c linux-2.6.0-test4-keys/kernel/user.c --- linux-2.6.0-test4/kernel/user.c 2003-08-26 13:27:21.000000000 +0100 +++ linux-2.6.0-test4-keys/kernel/user.c 2003-08-29 11:03:52.000000000 +0100 @@ -12,6 +12,7 @@ #include #include #include +#include /* * UID task count cache, to get fast user lookup in "alloc_uid" @@ -30,7 +31,10 @@ struct user_struct root_user = { .__count = ATOMIC_INIT(1), .processes = ATOMIC_INIT(1), - .files = ATOMIC_INIT(0) + .files = ATOMIC_INIT(0), +#ifdef CONFIG_KEYS + .keyring = &root_user_keyring, +#endif }; /* @@ -73,6 +77,7 @@ { if (up && atomic_dec_and_lock(&up->__count, &uidhash_lock)) { uid_hash_remove(up); + key_put(up->keyring); kmem_cache_free(uid_cachep, up); spin_unlock(&uidhash_lock); } @@ -98,6 +103,11 @@ atomic_set(&new->processes, 0); atomic_set(&new->files, 0); + if (alloc_uid_keyring(new) < 0) { + kmem_cache_free(uid_cachep, up); + return NULL; + } + /* * Before adding this, check whether we raced * on adding the same user already.. @@ -130,6 +140,7 @@ atomic_dec(&old_user->processes); current->user = new_user; free_uid(old_user); + suid_keys(current); } diff -uNr linux-2.6.0-test4/security/Kconfig linux-2.6.0-test4-keys/security/Kconfig --- linux-2.6.0-test4/security/Kconfig 2003-08-26 13:27:21.000000000 +0100 +++ linux-2.6.0-test4-keys/security/Kconfig 2003-08-29 10:13:32.000000000 +0100 @@ -4,6 +4,18 @@ menu "Security options" +config KEYS + bool "Enable access key retention support" + help + This option provides support for retaining authentication tokens and + access keys in the kernel. + + It also includes provision of methods by which such keys might be + associated with a process so that network filesystems, encryption + support and the like can find them. + + If you are unsure how to answer this question, answer N. + config SECURITY bool "Enable different security models" help diff -uNr linux-2.6.0-test4/security/keys/internal.h linux-2.6.0-test4-keys/security/keys/internal.h --- linux-2.6.0-test4/security/keys/internal.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test4-keys/security/keys/internal.h 2003-08-29 11:06:37.000000000 +0100 @@ -0,0 +1,75 @@ +/* internal.h: authentication token and access key management internal defs + * + * Copyright (C) 2003 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _INTERNAL_H +#define _INTERNAL_H + +#include + +/*****************************************************************************/ +/* + * list of subscribed keys + * - when plumbing the depths of the key tree, there's a hard limit set on the + * depth to deal with cycles in the tree + */ +struct keyring_list { + unsigned maxkeys; /* max keys this list can hold */ + unsigned nkeys; /* number of keys this list currently holds */ + struct key *keys[0]; +}; + +#define KEYRING_SEARCH_MAX_DEPTH 6 + +/*****************************************************************************/ +/* + * name of keyring + * - publicly available keyrings must be labelled uniquely + */ +struct keyring_name { + struct key *keyring; /* keyring key */ + struct rb_node name_node; /* node in tree of public names */ + char name[0]; /* label for this keyring */ +}; + +extern struct rb_root key_serial_tree; +extern spinlock_t key_serial_lock; +extern struct list_head key_types_list; +extern struct rw_semaphore key_types_sem; + +extern int key_alloc(struct key_type *type, struct key **_key); +extern int keyring_alloc(struct key *source, struct key **_key); + +extern int __keyring_add_key(struct key *keyring, struct key *key); + +extern int keyring_set_name(struct key *keyring, const char *fmt, ...) +__attribute__((format(printf, 2, 3))); + + +#ifdef KEY_DEBUGGING +static void __key_validate(const struct key *key) +{ + printk("__key_validate: key %p {%08x} should be {%08x}\n", + key, key->magic, KEY_DEBUG_MAGIC); + BUG(); +} + +static inline void key_validate(const struct key *key) +{ + if (key && key->magic != KEY_DEBUG_MAGIC) + __key_validate(key); +} + +#else +static inline void key_validate(const struct key *key) {} + +#endif + +#endif /* _INTERNAL_H */ diff -uNr linux-2.6.0-test4/security/keys/key.c linux-2.6.0-test4-keys/security/keys/key.c --- linux-2.6.0-test4/security/keys/key.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test4-keys/security/keys/key.c 2003-08-29 11:07:39.000000000 +0100 @@ -0,0 +1,595 @@ +/* key.c: authentication token and access key management + * + * Copyright (C) 2003 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include "internal.h" + +static kmem_cache_t *key_jar; +static key_serial_t key_serial_next = 2; +struct rb_root key_serial_tree; +spinlock_t key_serial_lock = SPIN_LOCK_UNLOCKED; + +static struct rb_root keyring_name_tree; +static rwlock_t keyring_name_lock = RW_LOCK_UNLOCKED; + +static int keyring_match(const struct key *key, const void *criterion); +static void keyring_clear(struct key *key); +static void keyring_describe(const struct key *keyring, struct seq_file *m); + +static struct key_type key_type_keyring = { + .name = "keyring", + .link = { &key_types_list, &key_types_list }, + .match = keyring_match, + .clear = keyring_clear, + .describe = keyring_describe, +}; + +struct list_head key_types_list = { + .next = &key_type_keyring.link, + .prev = &key_type_keyring.link, +}; +DECLARE_RWSEM(key_types_sem); + +struct key root_user_keyring = { + .usage = ATOMIC_INIT(1), + .serial = 1, + .type = &key_type_keyring, + .lock = RW_LOCK_UNLOCKED, + .sem = __RWSEM_INITIALIZER(root_user_keyring.sem), +#ifdef KEY_DEBUGGING + .magic = KEY_DEBUG_MAGIC, +#endif +}; + +/*****************************************************************************/ +/* + * allocate a key of the specified type + * - the key is provided with a unique serial number + */ +int key_alloc(struct key_type *type, struct key **_key) +{ + struct rb_node *parent, **p; + struct key *key, *xkey; + + *_key = NULL; + + key = kmem_cache_alloc(key_jar, SLAB_KERNEL); + if (!key) + return -ENOMEM; + + memset(key, 0, sizeof(*key)); + + atomic_set(&key->usage, 1); + rwlock_init(&key->lock); + init_rwsem(&key->sem); + key->type = type; +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC; +#endif + + spin_lock(&key_serial_lock); + + /* propose a serial number and try to insert it into the tree */ + key->serial = key_serial_next; + if (key->serial < 2) + key->serial = 2; + key_serial_next = key->serial + 1; + + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + goto serial_exists; + } + goto insert_here; + + /* we found a key with the proposed serial number - walk the tree from + * that point looking for the next unused serial number */ + serial_exists: + for (;;) { + key->serial = key_serial_next; + if (key->serial < 2) + key->serial = 2; + key_serial_next = key->serial + 1; + + if (!parent->rb_parent) + p = &key_serial_tree.rb_node; + else if (parent->rb_parent->rb_left == parent) + p = &parent->rb_parent->rb_left; + else + p = &parent->rb_parent->rb_right; + + parent = rb_next(parent); + if (!parent) + break; + + xkey = rb_entry(parent, struct key, serial_node); + if (key->serial < xkey->serial) + goto insert_here; + } + + insert_here: + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + *_key = key; + return 0; +} /* end key_alloc() */ + +/*****************************************************************************/ +/* + * dispose of a key + */ +void key_put(struct key *key) +{ + if (!key) + return; + + key_validate(key); + if (atomic_dec_and_lock(&key->usage, &key_serial_lock)) { + key->flags |= KEY_FLAG_DEAD; + rb_erase(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + key->type->clear(key); +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC_X; +#endif + kmem_cache_free(key_jar, key); + } + +} /* end key_put() */ + +EXPORT_SYMBOL(key_put); + +/*****************************************************************************/ +/* + * retire a key from service + */ +void key_retire(struct key *key) +{ + key_validate(key); + + down_write(&key->sem); + write_lock(&key->lock); + key->flags |= KEY_FLAG_RETIRED; + write_unlock(&key->lock); + up_write(&key->sem); +} /* end key_retire() */ + +EXPORT_SYMBOL(key_retire); + +/*****************************************************************************/ +/* + * allocate or duplicate a keyring + * - the new keyring does not get a name attached, even if duplicated + */ +int keyring_alloc(struct key *source, struct key **_key) +{ + struct keyring_list *sklist, *klist; + struct key *keyring; + unsigned max; + size_t size; + int ret, loop; + + ret = key_alloc(&key_type_keyring, _key); + if (ret < 0 || !source) + return ret; + + keyring = *_key; + key_validate(keyring); + + /* duplicate the list of subscribed keys */ + if (source->payload.subscriptions) { + const unsigned limit = + (PAGE_SIZE - sizeof(*klist)) / sizeof(struct key); + + /* prevent the source keyring from being altered */ + down_read(&source->sem); + + sklist = source->payload.subscriptions; + if (sklist && sklist->nkeys > 0) { + max = sklist->nkeys; + BUG_ON(max > limit); + + max = (max + 3) & ~3; + if (max > limit) + max = limit; + + size = sizeof(*klist) + sizeof(struct key) * max; + klist = kmalloc(size, GFP_KERNEL); + if (!klist) + goto nomem; + + klist->maxkeys = max; + klist->nkeys = sklist->nkeys; + memcpy(klist->keys, sklist->keys, + sklist->nkeys * sizeof(struct key)); + + for (loop = klist->nkeys - 1; loop >= 0; loop--) + atomic_inc(&klist->keys[loop]->usage); + + keyring->payload.subscriptions = klist; + } + + up_read(&source->sem); + } + + return ret; + + nomem: + up_read(&source->sem); + key_put(keyring); + *_key = NULL; + return -ENOMEM; +} /* end keyring_alloc() */ + +EXPORT_SYMBOL(keyring_alloc); + +/*****************************************************************************/ +/* + * search the supplied keyring tree for a key that matches the criterion + * - perform a depth-first search up to the prescribed limit + */ +int keyring_search(struct key *keyring, + const struct key_type *type, + const char *description, + struct key **_key) +{ + struct { + struct key *keyring; + struct keyring_list *keylist; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct key *key; + int sp, psp, kix; + + key_validate(keyring); + + *_key = NULL; + + if (keyring->type != &key_type_keyring) + return -EINVAL; + + sp = 0; + + descend: + read_lock(&keyring->lock); + if (keyring->flags & KEY_FLAG_RETIRED) + goto retired; + + keylist = keyring->payload.subscriptions; + kix = 0; + + ascend: + while (kix < keylist->nkeys) { + key = keylist->keys[kix]; + + if (key->type == type && key->type->match(key, description)) { + if (key->flags & KEY_FLAG_RETIRED) + goto next; + atomic_inc(&key->usage); + goto found; + } + + if (key->type == &key_type_keyring) { + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto next; + + /* evade loops in the keyring tree */ + for (psp = 0; psp < sp; psp++) + if (stack[psp].keyring == keyring) + goto next; + + stack[sp].keyring = keyring; + stack[sp].keylist = keylist; + stack[sp].kix = kix; + sp++; + goto descend; + } + + next: + kix++; + } + + retired: + read_unlock(&keyring->lock); + + if (sp > 0) { + sp--; + keyring = stack[sp].keyring; + keylist = stack[sp].keylist; + kix = stack[sp].kix + 1; + goto ascend; + } + + return -ENOENT; + + found: + read_unlock(&keyring->lock); + + for (; sp > 0; sp--) + read_unlock(&stack[sp].keyring->lock); + + key_validate(key); + + *_key = key; + return 0; +} /* end keyring_search() */ + +EXPORT_SYMBOL(keyring_search); + +/*****************************************************************************/ +/* + * add a key to a keyring + */ +int __keyring_add_key(struct key *keyring, struct key *key) +{ + struct keyring_list *klist, *nklist; + unsigned max; + size_t size; + + if (keyring->flags & KEY_FLAG_RETIRED) + return -EINVAL; + + klist = keyring->payload.subscriptions; + if (klist && klist->nkeys < klist->maxkeys) { + atomic_inc(&key->usage); + + write_lock(&keyring->lock); + klist->keys[klist->nkeys++] = key; + write_unlock(&keyring->lock); + + return 0; + } + + /* grow the key list */ + max = 4; + if (klist) + max += klist->maxkeys; + + size = sizeof(*klist) + sizeof(*key) * max; + if (size > PAGE_SIZE) + return -ENFILE; + + nklist = kmalloc(size, GFP_KERNEL); + if (!nklist) + return -ENOMEM; + nklist->maxkeys = max; + nklist->nkeys = 0; + + if (klist) { + nklist->nkeys = klist->nkeys; + memcpy(nklist->keys, klist->keys, sizeof(*key) * klist->nkeys); + } + + atomic_inc(&key->usage); + + write_lock(&keyring->lock); + keyring->payload.subscriptions = nklist; + nklist->keys[nklist->nkeys++] = key; + write_unlock(&keyring->lock); + + if (klist) + kfree(klist); + return 0; + +} /* end __keyring_add_key() */ + +/*****************************************************************************/ +/* + * add a key to a keyring + */ +int keyring_add_key(struct key *keyring, struct key *key) +{ + int ret; + + key_validate(keyring); + key_validate(key); + + down_write(&keyring->sem); + ret = __keyring_add_key(keyring, key); + up_write(&keyring->sem); + + return ret; +} /* end keyring_add_key() */ + +EXPORT_SYMBOL(keyring_add_key); + +/*****************************************************************************/ +/* + * set the name of a keyring + */ +int keyring_set_name(struct key *keyring, const char *fmt, ...) +{ + struct keyring_name *kname; + va_list va; + size_t len; + char buf[32]; + + key_validate(keyring); + + if (keyring->type != &key_type_keyring) + return -EINVAL; + + va_start(va, fmt); + vsnprintf(buf, 32, fmt, va); + va_end(va); + len = strlen(buf); + + kname = kmalloc(sizeof(*kname) + len + 1, GFP_KERNEL); + if (!kname) + return -ENOMEM; + memset(kname, 0, sizeof(*kname)); + memcpy(kname->name, buf, len + 1); + kname->keyring = keyring; + + down_write(&keyring->sem); + write_lock(&keyring->lock); + keyring->description.ringname = kname; + write_unlock(&keyring->lock); + up_write(&keyring->sem); + + return 0; +} /* end keyring_set_name() */ + +EXPORT_SYMBOL(keyring_set_name); + +/*****************************************************************************/ +/* + * match keyrings on their name + */ +static int keyring_match(const struct key *keyring, const void *description) +{ + struct keyring_name *kname; + + kname = keyring->description.ringname; + if (kname) + if (strcmp(kname->name, description) == 0) + return 1; + + return 0; +} /* end keyring_match() */ + +/*****************************************************************************/ +/* + * dispose of the data dangling from the corpse of a keyring + */ +static void keyring_clear(struct key *keyring) +{ + struct keyring_name *kname; + struct keyring_list *klist; + int loop; + + kname = keyring->description.ringname; + if (kname) { + if (keyring->flags & KEY_FLAG_PUBLIC_KEYRING) { + write_lock(&keyring_name_lock); + rb_erase(&kname->name_node, &keyring_name_tree); + write_unlock(&keyring_name_lock); + } + + kfree(kname); + } + + klist = keyring->payload.subscriptions; + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + kfree(klist); + } + +} /* end keyring_clear() */ + +/*****************************************************************************/ +/* + * describe the keyring + */ +static void keyring_describe(const struct key *keyring, struct seq_file *m) +{ + struct keyring_name *kname; + struct keyring_list *klist; + + kname = keyring->description.ringname; + if (kname) { + seq_printf(m, "%s", kname->name); + } + else { + seq_puts(m, "[anon]"); + } + + klist = keyring->payload.subscriptions; + if (klist) + seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); + else + seq_puts(m, ": empty"); + +} /* end keyring_describe() */ + +/*****************************************************************************/ +/* + * register a type of key + */ +int register_key_type(struct key_type *ktype) +{ + struct key_type *p; + int ret; + + ret = -EEXIST; + down_write(&key_types_sem); + + list_for_each_entry(p, &key_types_list, link) { + if (strcmp(p->name, ktype->name) == 0) + goto out; + } + + list_add(&ktype->link, &key_types_list); + ret = 0; + + out: + up_write(&key_types_sem); + return ret; +} /* end register_key_type() */ + +EXPORT_SYMBOL(register_key_type); + +/*****************************************************************************/ +/* + * unregister a type of key + */ +void unregister_key_type(struct key_type *ktype) +{ + down_write(&key_types_sem); + + list_del_init(&ktype->link); + + up_write(&key_types_sem); +} /* end unregister_key_type() */ + +EXPORT_SYMBOL(unregister_key_type); + +/*****************************************************************************/ +/* + * initialise the key management stuff + */ +static int __init key_init(void) +{ + key_jar = kmem_cache_create("key_jar", sizeof(struct key), + 0, + SLAB_HWCACHE_ALIGN, NULL, NULL); + if (!key_jar) + panic("Cannot create key jar\n"); + + key_validate(&root_user_keyring); + rb_link_node(&root_user_keyring.serial_node, NULL, &key_serial_tree.rb_node); + rb_insert_color(&root_user_keyring.serial_node, &key_serial_tree); + keyring_set_name(&root_user_keyring, "_uid.0"); + + return 0; +} /* end key_init() */ + +subsys_initcall(key_init); diff -uNr linux-2.6.0-test4/security/keys/Makefile linux-2.6.0-test4-keys/security/keys/Makefile --- linux-2.6.0-test4/security/keys/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test4-keys/security/keys/Makefile 2003-09-05 14:47:55.000000000 +0100 @@ -0,0 +1,9 @@ +# +# Makefile for key management +# + +obj-y := \ + key.o \ + process_keys.o + +obj-$(CONFIG_PROC_FS) += proc.o diff -uNr linux-2.6.0-test4/security/keys/proc.c linux-2.6.0-test4-keys/security/keys/proc.c --- linux-2.6.0-test4/security/keys/proc.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test4-keys/security/keys/proc.c 2003-08-29 10:20:14.000000000 +0100 @@ -0,0 +1,119 @@ +/* proc.c: proc files for key database enumeration + * + * Copyright (C) 2003 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +static int proc_keys_open(struct inode *inode, struct file *file); +static void *proc_keys_start(struct seq_file *p, loff_t *_pos); +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_keys_stop(struct seq_file *p, void *v); +static int proc_keys_show(struct seq_file *m, void *v); + +static struct seq_operations proc_keys_ops = { + .start = proc_keys_start, + .next = proc_keys_next, + .stop = proc_keys_stop, + .show = proc_keys_show, +}; + +static struct file_operations proc_keys_fops = { + .open = proc_keys_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/*****************************************************************************/ +/* + * implement "/proc/keys" to provides a list of the keys on the system + */ +static int proc_keys_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_keys_ops); + +} + +static void *proc_keys_start(struct seq_file *p, loff_t *_pos) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_serial_lock); + + _p = rb_first(&key_serial_tree); + while (pos > 0 && _p) { + pos--; + _p = rb_next(_p); + } + + return _p; +} + +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return rb_next((struct rb_node *) v); +} + +static void proc_keys_stop(struct seq_file *p, void *v) +{ + spin_unlock(&key_serial_lock); +} + +static int proc_keys_show(struct seq_file *m, void *v) +{ + struct rb_tree *_p = v; + struct key *key = rb_entry(_p, struct key, serial_node); + + read_lock(&key->lock); + + seq_printf(m, "%08x %c%c%c %5d %-9.9s ", + key->serial, + key->flags & KEY_FLAG_PUBLIC_KEYRING ? 'p' : '-', + key->flags & KEY_FLAG_RETIRED ? 'r' : '-', + key->flags & KEY_FLAG_DEAD ? 'd' : '-', + atomic_read(&key->usage), + key->type->name); + + key->type->describe(key, m); + seq_putc(m, '\n'); + + read_unlock(&key->lock); + + return 0; +} + +/*****************************************************************************/ +/* + * declare the /proc files + */ +static int __init key_proc_init(void) +{ + struct proc_dir_entry *p; + + p = create_proc_entry("keys", 0, NULL); + if (!p) + panic("Cannot create /proc/keys\n"); + + p->proc_fops = &proc_keys_fops; + + return 0; +} /* end key_proc_init() */ + +__initcall(key_proc_init); diff -uNr linux-2.6.0-test4/security/keys/process_keys.c linux-2.6.0-test4-keys/security/keys/process_keys.c --- linux-2.6.0-test4/security/keys/process_keys.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test4-keys/security/keys/process_keys.c 2003-08-29 11:16:53.000000000 +0100 @@ -0,0 +1,491 @@ +/* process_keys.c: management of process keyrings + * + * Copyright (C) 2003 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +/*****************************************************************************/ +/* + * allocate a keyring to associate with a UID + */ +int alloc_uid_keyring(struct user_struct *user) +{ + struct key *keyring; + int ret; + + ret = keyring_alloc(NULL, &keyring); + if (ret < 0) + return ret; + + ret = keyring_set_name(keyring, "_uid.%u", user->uid); + if (ret < 0) { + key_put(keyring); + return ret; + } + + user->keyring = keyring; + return 0; +} /* end alloc_uid_keyring() */ + +/*****************************************************************************/ +/* + * install a fresh thread keyring, discarding the old one + */ +static int install_thread_keyring(struct task_struct *tsk) +{ + int ret; + + key_put(xchg(&tsk->thread_keyring, NULL)); + + ret = keyring_alloc(NULL, &tsk->thread_keyring); + if (ret < 0) + return ret; + + return keyring_set_name(tsk->thread_keyring, "_tid.%d", tsk->pid); + +} /* end install_thread_keyring() */ + +/*****************************************************************************/ +/* + * install a fresh process keyring, discarding the old one + */ +static int install_process_keyring(struct task_struct *tsk) +{ + int ret; + + key_put(xchg(&tsk->process_keyring, NULL)); + + ret = keyring_alloc(NULL, &tsk->process_keyring); + if (ret < 0) + return ret; + + return keyring_set_name(tsk->process_keyring, "_pid.%d", tsk->tgid); + +} /* end install_process_keyring() */ + +/*****************************************************************************/ +/* + * install a fresh session keyring, discarding the old one + */ +static int install_session_keyring(struct task_struct *tsk) +{ + struct key *keyring; + int ret; + + ret = keyring_alloc(NULL, &keyring); + if (ret < 0) + return ret; + + ret = keyring_set_name(keyring, "_ses.%u", keyring->serial); + if (ret < 0) + return ret; + + key_put(xchg(&tsk->session_keyring, keyring)); + + return 0; +} /* end install_session_keyring() */ + +/*****************************************************************************/ +/* + * select the keyring specified by the user, making sure it exists + */ +static int select_keyring(int specifier, struct key **_keyring) +{ + struct task_struct *tsk = current; + int ret; + + switch (specifier) { + case PR_SPEC_THREAD_KEYRING: + if (!tsk->thread_keyring) { + ret = install_thread_keyring(tsk); + if (ret < 0) + return ret; + } + *_keyring = tsk->thread_keyring; + return 0; + + case PR_SPEC_PROCESS_KEYRING: + if (!tsk->process_keyring) { + ret = install_process_keyring(tsk); + if (ret < 0) + return ret; + } + *_keyring = tsk->process_keyring; + return 0; + + case PR_SPEC_SESSION_KEYRING: + if (!tsk->session_keyring) { + ret = install_session_keyring(tsk); + if (ret < 0) + return ret; + } + *_keyring = tsk->session_keyring; + return 0; + + case PR_SPEC_USER_KEYRING: + *_keyring = tsk->user->keyring; + return 0; + + case PR_SPEC_GROUP_KEYRING: + default: + return -EINVAL; + } +} /* end select_keyring() */ + +/*****************************************************************************/ +/* + * get the ID of the specified process keyring + * - implements prctl(PR_GET_KEYRING_ID) + */ +long get_process_keyring_ID(int specifier) +{ + struct key *keyring; + int ret; + + ret = select_keyring(specifier, &keyring); + + return ret < 0 ? ret : keyring->serial; + +} /* end get_process_keyring_ID() */ + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - implements prctl(PR_CLEAR_KEYRING) + */ +long clear_process_keyring(int specifier) +{ + struct keyring_list *klist; + struct task_struct *tsk = current; + struct key *keyring; + int loop; + + switch (specifier) { + case PR_SPEC_THREAD_KEYRING: + if (!tsk->thread_keyring) + return 0; + keyring = tsk->thread_keyring; + break; + + case PR_SPEC_PROCESS_KEYRING: + if (!tsk->process_keyring) + return 0; + keyring = tsk->process_keyring; + break; + + case PR_SPEC_SESSION_KEYRING: + if (!tsk->session_keyring) + return 0; + keyring = tsk->session_keyring; + break; + + case PR_SPEC_USER_KEYRING: + keyring = tsk->user->keyring; + + case PR_SPEC_GROUP_KEYRING: + default: + return -EINVAL; + } + + down_write(&keyring->sem); + klist = keyring->payload.subscriptions; + if (klist) { + write_lock(&keyring->lock); + keyring->payload.subscriptions = NULL; + write_unlock(&keyring->lock); + } + up_write(&keyring->sem); + + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + + kfree(klist); + } + + return 0; +} /* end clear_process_keyring() */ + +/*****************************************************************************/ +/* + * install a new session keyring, discarding the old one + * - implements prctl(PR_NEW_SESSION_KEYRING) + */ +long new_session_keyring(void) +{ + int ret; + + ret = install_session_keyring(current); + if (ret < 0) + return ret; + + return current->session_keyring ? current->session_keyring->serial : 0; + +} /* end new_session_keyring() */ + +/*****************************************************************************/ +/* + * add a new key to the specified process keyring + */ +int add_process_key(int specifier, + const char *type, + const char *description, + int plen, + const void *payload) +{ + struct keyring_list *klist; + struct key_type *ktype; + struct key *keyring, *key; + int loop, ret; + + ret = select_keyring(specifier, &keyring); + if (ret < 0) + return ret; + + down_read(&key_types_sem); + + /* look up the type */ + ret = -ENOENT; + list_for_each_entry(ktype, &key_types_list, link) { + if (strcmp(ktype->name, type) == 0) + goto found_type; + } + goto error; + + found_type: + ret = -EISDIR; + if (!ktype->init) + goto error; + + /* search for an existing key of the same type and description */ + down_write(&keyring->sem); + + klist = keyring->payload.subscriptions; + if (klist) { + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + if (key->type == ktype && + key->type->match(key, description) && + !key->flags & KEY_FLAG_RETIRED) + goto update; + } + } + + /* generate a new key and initialise it */ + ret = key_alloc(ktype, &key); + if (ret < 0) + goto error2; + + ret = ktype->init(key, description, plen, payload); + if (ret < 0) + goto error3; + + ret = __keyring_add_key(keyring, key); + + error3: + key_put(key); + error2: + up_write(&keyring->sem); + error: + up_read(&key_types_sem); + return ret; + + /* update an existing key */ + update: + ret = -EEXIST; + if (ktype->update) + ret = ktype->update(key, description, plen, payload); + goto error2; + +} /* end add_process_key() */ + +EXPORT_SYMBOL(add_process_key); + +/*****************************************************************************/ +/* + * extract the description of a new key from userspace and add it as a key to + * one of the process's keyrings + * - implements prctl(PR_ADD_NEW_KEY) + */ +long add_user_key(int specifier, + char __user *_type, + char __user *_description, + void __user *_payload) +{ + char type[32], *description; + void *payload; + long dlen; + int ret, plen; + + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + return ret; + type[31] = '\0'; + + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + return -EFAULT; + if (dlen > PAGE_SIZE - 1) + return -EINVAL; + + description = kmalloc(dlen, GFP_KERNEL); + if (!description) + return -ENOMEM; + ret = -EFAULT; + if (copy_from_user(description, _description, dlen) != 0) + goto error; + + ret = get_user(plen, (uint16_t *) _payload); + if (ret < 0) + goto error; + _payload += 2; + + ret = -EINVAL; + if (plen < 0 || plen > PAGE_SIZE) + goto error; + + ret = -ENOMEM; + payload = (void *) get_zeroed_page(GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + + ret = add_process_key(specifier, type, description, plen, payload); + + error2: + free_page((unsigned long) payload); + error: + kfree(description); + return ret; +} /* end add_user_key() */ + +/*****************************************************************************/ +/* + * copy the keys for fork + */ +int copy_keys(unsigned long clone_flags, struct task_struct *tsk) +{ + int ret = 0; + + key_validate(tsk->session_keyring); + key_validate(tsk->process_keyring); + key_validate(tsk->thread_keyring); + + if (tsk->session_keyring) + atomic_inc(&tsk->session_keyring->usage); + + if (tsk->process_keyring) { + if (clone_flags & CLONE_THREAD) { + atomic_inc(&tsk->process_keyring->usage); + } + else { + tsk->process_keyring = NULL; + ret = install_process_keyring(tsk); + } + } + + tsk->thread_keyring = NULL; + + return ret; +} /* end copy_keys() */ + +/*****************************************************************************/ +/* + * dispose of keys upon exit + */ +void exit_keys(struct task_struct *tsk) +{ + key_put(tsk->session_keyring); + key_put(tsk->process_keyring); + key_put(tsk->thread_keyring); + +} /* end exit_keys() */ + +/*****************************************************************************/ +/* + * deal with SUID programs and setuid()/setreuid()/setresuid() + */ +int suid_keys(struct task_struct *tsk) +{ + return tsk->session_keyring ? install_session_keyring(tsk) : 0; + +} /* end suid_keys() */ + +/*****************************************************************************/ +/* + * deal with execve() + */ +int exec_keys(struct task_struct *tsk) +{ + key_put(xchg(&tsk->thread_keyring, NULL)); + + if (!tsk->session_keyring) + if (install_session_keyring(tsk) < 0) + return -ENOMEM; + + return install_process_keyring(tsk); + +} /* end exec_keys() */ + +/*****************************************************************************/ +/* + * search selected process keyrings for the first matching key + */ +int search_process_keyrings(unsigned search_mask, + const struct key_type *type, + const char *description, + struct key **_key) +{ + struct task_struct *tsk = current; + int ret; + + if (search_mask & SEARCH_KEYRING_THREAD && !tsk->thread_keyring) { + ret = keyring_search(tsk->thread_keyring, + type, description, _key); + if (ret == 0) + return ret; + } + + if (search_mask & SEARCH_KEYRING_PROCESS && !tsk->process_keyring) { + ret = keyring_search(tsk->process_keyring, + type, description, _key); + if (ret == 0) + return ret; + } + + if (search_mask & SEARCH_KEYRING_SESSION && !tsk->session_keyring) { + ret = keyring_search(tsk->session_keyring, + type, description, _key); + if (ret == 0) + return ret; + } + + if (search_mask & SEARCH_KEYRING_UID && !tsk->user->keyring) { + ret = keyring_search(tsk->user->keyring, + type, description, _key); + if (ret == 0) + return ret; + } + + return -ENOENT; +} /* end search_process_keyrings() */ diff -uNr linux-2.6.0-test4/security/Makefile linux-2.6.0-test4-keys/security/Makefile --- linux-2.6.0-test4/security/Makefile 2003-08-26 13:27:21.000000000 +0100 +++ linux-2.6.0-test4-keys/security/Makefile 2003-08-29 10:31:49.000000000 +0100 @@ -2,6 +2,7 @@ # Makefile for the kernel security code # +obj-$(CONFIG_KEYS) += keys/ subdir-$(CONFIG_SECURITY_SELINUX) += selinux # if we don't select a security model, use the default capabilities - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/