CRED: Detach the credentials from task_struct
David Howells [Thu, 13 Nov 2008 23:39:17 +0000 (10:39 +1100)]
Detach the credentials from task_struct, duplicating them in copy_process()
and releasing them in __put_task_struct().

Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: James Morris <jmorris@namei.org>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Signed-off-by: James Morris <jmorris@namei.org>

include/linux/cred.h
include/linux/init_task.h
include/linux/sched.h
include/linux/security.h
kernel/Makefile
kernel/cred.c [new file with mode: 0644]
kernel/fork.c
security/capability.c
security/security.c
security/selinux/hooks.c
security/smack/smack_lsm.c

index 3e65587..a7a6860 100644 (file)
@@ -158,4 +158,33 @@ do {                                               \
        *(_gid) = current->cred->fsgid;         \
 } while(0)
 
+extern void __put_cred(struct cred *);
+extern int copy_creds(struct task_struct *, unsigned long);
+
+/**
+ * get_cred - Get a reference on a set of credentials
+ * @cred: The credentials to reference
+ *
+ * Get a reference on the specified set of credentials.  The caller must
+ * release the reference.
+ */
+static inline struct cred *get_cred(struct cred *cred)
+{
+       atomic_inc(&cred->usage);
+       return cred;
+}
+
+/**
+ * put_cred - Release a reference to a set of credentials
+ * @cred: The credentials to release
+ *
+ * Release a reference to a set of credentials, deleting them when the last ref
+ * is released.
+ */
+static inline void put_cred(struct cred *cred)
+{
+       if (atomic_dec_and_test(&(cred)->usage))
+               __put_cred(cred);
+}
+
 #endif /* _LINUX_CRED_H */
index 9de41cc..5e24c54 100644 (file)
@@ -115,19 +115,6 @@ extern struct group_info init_groups;
 
 extern struct cred init_cred;
 
-#define INIT_CRED(p)                                           \
-{                                                              \
-       .usage                  = ATOMIC_INIT(3),               \
-       .securebits             = SECUREBITS_DEFAULT,           \
-       .cap_inheritable        = CAP_INIT_INH_SET,             \
-       .cap_permitted          = CAP_FULL_SET,                 \
-       .cap_effective          = CAP_INIT_EFF_SET,             \
-       .cap_bset               = CAP_INIT_BSET,                \
-       .user                   = INIT_USER,                    \
-       .group_info             = &init_groups,                 \
-       .lock                   = __SPIN_LOCK_UNLOCKED(p.lock), \
-}
-
 /*
  *  INIT_TASK is used to set up the first task table, touch at
  * your own risk!. Base=0, limit=0x1fffff (=2MB)
@@ -162,8 +149,7 @@ extern struct cred init_cred;
        .children       = LIST_HEAD_INIT(tsk.children),                 \
        .sibling        = LIST_HEAD_INIT(tsk.sibling),                  \
        .group_leader   = &tsk,                                         \
-       .__temp_cred    = INIT_CRED(tsk.__temp_cred),                   \
-       .cred           = &tsk.__temp_cred,                             \
+       .cred           = &init_cred,                                   \
        .comm           = "swapper",                                    \
        .thread         = INIT_THREAD,                                  \
        .fs             = &init_fs,                                     \
index c8b9250..740cf94 100644 (file)
@@ -1151,7 +1151,6 @@ struct task_struct {
        struct list_head cpu_timers[3];
 
 /* process credentials */
-       struct cred __temp_cred __deprecated; /* temporary credentials to be removed */
        struct cred *cred;      /* actual/objective task credentials */
 
        char comm[TASK_COMM_LEN]; /* executable name excluding path
index 9f305d4..9239cc1 100644 (file)
@@ -593,15 +593,15 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
  *     manual page for definitions of the @clone_flags.
  *     @clone_flags contains the flags indicating what should be shared.
  *     Return 0 if permission is granted.
- * @task_alloc_security:
- *     @p contains the task_struct for child process.
- *     Allocate and attach a security structure to the p->security field. The
- *     security field is initialized to NULL when the task structure is
+ * @cred_alloc_security:
+ *     @cred contains the cred struct for child process.
+ *     Allocate and attach a security structure to the cred->security field.
+ *     The security field is initialized to NULL when the task structure is
  *     allocated.
  *     Return 0 if operation was successful.
- * @task_free_security:
- *     @p contains the task_struct for process.
- *     Deallocate and clear the p->security field.
+ * @cred_free:
+ *     @cred points to the credentials.
+ *     Deallocate and clear the cred->security field in a set of credentials.
  * @task_setuid:
  *     Check permission before setting one or more of the user identity
  *     attributes of the current process.  The @flags parameter indicates
@@ -1405,8 +1405,8 @@ struct security_operations {
        int (*dentry_open) (struct file *file);
 
        int (*task_create) (unsigned long clone_flags);
-       int (*task_alloc_security) (struct task_struct *p);
-       void (*task_free_security) (struct task_struct *p);
+       int (*cred_alloc_security) (struct cred *cred);
+       void (*cred_free) (struct cred *cred);
        int (*task_setuid) (uid_t id0, uid_t id1, uid_t id2, int flags);
        int (*task_post_setuid) (uid_t old_ruid /* or fsuid */ ,
                                 uid_t old_euid, uid_t old_suid, int flags);
@@ -1660,8 +1660,8 @@ int security_file_send_sigiotask(struct task_struct *tsk,
 int security_file_receive(struct file *file);
 int security_dentry_open(struct file *file);
 int security_task_create(unsigned long clone_flags);
-int security_task_alloc(struct task_struct *p);
-void security_task_free(struct task_struct *p);
+int security_cred_alloc(struct cred *cred);
+void security_cred_free(struct cred *cred);
 int security_task_setuid(uid_t id0, uid_t id1, uid_t id2, int flags);
 int security_task_post_setuid(uid_t old_ruid, uid_t old_euid,
                              uid_t old_suid, int flags);
@@ -2181,12 +2181,12 @@ static inline int security_task_create(unsigned long clone_flags)
        return 0;
 }
 
-static inline int security_task_alloc(struct task_struct *p)
+static inline int security_cred_alloc(struct cred *cred)
 {
        return 0;
 }
 
-static inline void security_task_free(struct task_struct *p)
+static inline void security_cred_free(struct cred *cred)
 { }
 
 static inline int security_task_setuid(uid_t id0, uid_t id1, uid_t id2,
index 9a3ec66..5a6a612 100644 (file)
@@ -9,7 +9,7 @@ obj-y     = sched.o fork.o exec_domain.o panic.o printk.o \
            rcupdate.o extable.o params.o posix-timers.o \
            kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o \
            hrtimer.o rwsem.o nsproxy.o srcu.o semaphore.o \
-           notifier.o ksysfs.o pm_qos_params.o sched_clock.o
+           notifier.o ksysfs.o pm_qos_params.o sched_clock.o cred.o
 
 CFLAGS_REMOVE_sched.o = -mno-spe
 
diff --git a/kernel/cred.c b/kernel/cred.c
new file mode 100644 (file)
index 0000000..833244a
--- /dev/null
@@ -0,0 +1,96 @@
+/* Task credentials management
+ *
+ * Copyright (C) 2008 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 Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/cred.h>
+#include <linux/sched.h>
+#include <linux/key.h>
+#include <linux/keyctl.h>
+#include <linux/init_task.h>
+#include <linux/security.h>
+
+/*
+ * The initial credentials for the initial task
+ */
+struct cred init_cred = {
+       .usage                  = ATOMIC_INIT(3),
+       .securebits             = SECUREBITS_DEFAULT,
+       .cap_inheritable        = CAP_INIT_INH_SET,
+       .cap_permitted          = CAP_FULL_SET,
+       .cap_effective          = CAP_INIT_EFF_SET,
+       .cap_bset               = CAP_INIT_BSET,
+       .user                   = INIT_USER,
+       .group_info             = &init_groups,
+};
+
+/*
+ * The RCU callback to actually dispose of a set of credentials
+ */
+static void put_cred_rcu(struct rcu_head *rcu)
+{
+       struct cred *cred = container_of(rcu, struct cred, rcu);
+
+       BUG_ON(atomic_read(&cred->usage) != 0);
+
+       key_put(cred->thread_keyring);
+       key_put(cred->request_key_auth);
+       put_group_info(cred->group_info);
+       free_uid(cred->user);
+       security_cred_free(cred);
+       kfree(cred);
+}
+
+/**
+ * __put_cred - Destroy a set of credentials
+ * @sec: The record to release
+ *
+ * Destroy a set of credentials on which no references remain.
+ */
+void __put_cred(struct cred *cred)
+{
+       call_rcu(&cred->rcu, put_cred_rcu);
+}
+EXPORT_SYMBOL(__put_cred);
+
+/*
+ * Copy credentials for the new process created by fork()
+ */
+int copy_creds(struct task_struct *p, unsigned long clone_flags)
+{
+       struct cred *pcred;
+       int ret;
+
+       pcred = kmemdup(p->cred, sizeof(*p->cred), GFP_KERNEL);
+       if (!pcred)
+               return -ENOMEM;
+
+#ifdef CONFIG_SECURITY
+       pcred->security = NULL;
+#endif
+
+       ret = security_cred_alloc(pcred);
+       if (ret < 0) {
+               kfree(pcred);
+               return ret;
+       }
+
+       atomic_set(&pcred->usage, 1);
+       get_group_info(pcred->group_info);
+       get_uid(pcred->user);
+       key_get(pcred->thread_keyring);
+       key_get(pcred->request_key_auth);
+
+       atomic_inc(&pcred->user->processes);
+
+       /* RCU assignment is unneeded here as no-one can have accessed this
+        * pointer yet, barring us */
+       p->cred = pcred;
+       return 0;
+}
index 81fdc77..c932e28 100644 (file)
@@ -146,9 +146,7 @@ void __put_task_struct(struct task_struct *tsk)
        WARN_ON(atomic_read(&tsk->usage));
        WARN_ON(tsk == current);
 
-       security_task_free(tsk);
-       free_uid(tsk->__temp_cred.user);
-       put_group_info(tsk->__temp_cred.group_info);
+       put_cred(tsk->cred);
        delayacct_tsk_free(tsk);
 
        if (!profile_handoff_task(tsk))
@@ -969,7 +967,6 @@ static struct task_struct *copy_process(unsigned long clone_flags,
        DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
        DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
 #endif
-       p->cred = &p->__temp_cred;
        retval = -EAGAIN;
        if (atomic_read(&p->cred->user->processes) >=
                        p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
@@ -978,9 +975,9 @@ static struct task_struct *copy_process(unsigned long clone_flags,
                        goto bad_fork_free;
        }
 
-       atomic_inc(&p->cred->user->__count);
-       atomic_inc(&p->cred->user->processes);
-       get_group_info(p->cred->group_info);
+       retval = copy_creds(p, clone_flags);
+       if (retval < 0)
+               goto bad_fork_free;
 
        /*
         * If multiple threads are within copy_process(), then this check
@@ -1035,9 +1032,6 @@ static struct task_struct *copy_process(unsigned long clone_flags,
        do_posix_clock_monotonic_gettime(&p->start_time);
        p->real_start_time = p->start_time;
        monotonic_to_bootbased(&p->real_start_time);
-#ifdef CONFIG_SECURITY
-       p->cred->security = NULL;
-#endif
        p->io_context = NULL;
        p->audit_context = NULL;
        cgroup_fork(p);
@@ -1082,10 +1076,8 @@ static struct task_struct *copy_process(unsigned long clone_flags,
        /* Perform scheduler related setup. Assign this task to a CPU. */
        sched_fork(p, clone_flags);
 
-       if ((retval = security_task_alloc(p)))
-               goto bad_fork_cleanup_policy;
        if ((retval = audit_alloc(p)))
-               goto bad_fork_cleanup_security;
+               goto bad_fork_cleanup_policy;
        /* copy all the process information */
        if ((retval = copy_semundo(clone_flags, p)))
                goto bad_fork_cleanup_audit;
@@ -1284,8 +1276,6 @@ bad_fork_cleanup_semundo:
        exit_sem(p);
 bad_fork_cleanup_audit:
        audit_free(p);
-bad_fork_cleanup_security:
-       security_task_free(p);
 bad_fork_cleanup_policy:
 #ifdef CONFIG_NUMA
        mpol_put(p->mempolicy);
@@ -1298,9 +1288,7 @@ bad_fork_cleanup_cgroup:
 bad_fork_cleanup_put_domain:
        module_put(task_thread_info(p)->exec_domain->module);
 bad_fork_cleanup_count:
-       put_group_info(p->cred->group_info);
-       atomic_dec(&p->cred->user->processes);
-       free_uid(p->cred->user);
+       put_cred(p->cred);
 bad_fork_free:
        free_task(p);
 fork_out:
index 2458748..6c4b513 100644 (file)
@@ -340,12 +340,12 @@ static int cap_task_create(unsigned long clone_flags)
        return 0;
 }
 
-static int cap_task_alloc_security(struct task_struct *p)
+static int cap_cred_alloc_security(struct cred *cred)
 {
        return 0;
 }
 
-static void cap_task_free_security(struct task_struct *p)
+static void cap_cred_free(struct cred *cred)
 {
 }
 
@@ -890,8 +890,8 @@ void security_fixup_ops(struct security_operations *ops)
        set_to_cap_if_null(ops, file_receive);
        set_to_cap_if_null(ops, dentry_open);
        set_to_cap_if_null(ops, task_create);
-       set_to_cap_if_null(ops, task_alloc_security);
-       set_to_cap_if_null(ops, task_free_security);
+       set_to_cap_if_null(ops, cred_alloc_security);
+       set_to_cap_if_null(ops, cred_free);
        set_to_cap_if_null(ops, task_setuid);
        set_to_cap_if_null(ops, task_post_setuid);
        set_to_cap_if_null(ops, task_setgid);
index 81c956a..d058f7d 100644 (file)
@@ -616,14 +616,14 @@ int security_task_create(unsigned long clone_flags)
        return security_ops->task_create(clone_flags);
 }
 
-int security_task_alloc(struct task_struct *p)
+int security_cred_alloc(struct cred *cred)
 {
-       return security_ops->task_alloc_security(p);
+       return security_ops->cred_alloc_security(cred);
 }
 
-void security_task_free(struct task_struct *p)
+void security_cred_free(struct cred *cred)
 {
-       security_ops->task_free_security(p);
+       security_ops->cred_free(cred);
 }
 
 int security_task_setuid(uid_t id0, uid_t id1, uid_t id2, int flags)
index 328308f..658435d 100644 (file)
@@ -158,7 +158,7 @@ static int selinux_secmark_enabled(void)
 
 /* Allocate and free functions for each kind of security blob. */
 
-static int task_alloc_security(struct task_struct *task)
+static int cred_alloc_security(struct cred *cred)
 {
        struct task_security_struct *tsec;
 
@@ -167,18 +167,11 @@ static int task_alloc_security(struct task_struct *task)
                return -ENOMEM;
 
        tsec->osid = tsec->sid = SECINITSID_UNLABELED;
-       task->cred->security = tsec;
+       cred->security = tsec;
 
        return 0;
 }
 
-static void task_free_security(struct task_struct *task)
-{
-       struct task_security_struct *tsec = task->cred->security;
-       task->cred->security = NULL;
-       kfree(tsec);
-}
-
 static int inode_alloc_security(struct inode *inode)
 {
        struct task_security_struct *tsec = current->cred->security;
@@ -3184,17 +3177,17 @@ static int selinux_task_create(unsigned long clone_flags)
        return task_has_perm(current, current, PROCESS__FORK);
 }
 
-static int selinux_task_alloc_security(struct task_struct *tsk)
+static int selinux_cred_alloc_security(struct cred *cred)
 {
        struct task_security_struct *tsec1, *tsec2;
        int rc;
 
        tsec1 = current->cred->security;
 
-       rc = task_alloc_security(tsk);
+       rc = cred_alloc_security(cred);
        if (rc)
                return rc;
-       tsec2 = tsk->cred->security;
+       tsec2 = cred->security;
 
        tsec2->osid = tsec1->osid;
        tsec2->sid = tsec1->sid;
@@ -3208,9 +3201,14 @@ static int selinux_task_alloc_security(struct task_struct *tsk)
        return 0;
 }
 
-static void selinux_task_free_security(struct task_struct *tsk)
+/*
+ * detach and free the LSM part of a set of credentials
+ */
+static void selinux_cred_free(struct cred *cred)
 {
-       task_free_security(tsk);
+       struct task_security_struct *tsec = cred->security;
+       cred->security = NULL;
+       kfree(tsec);
 }
 
 static int selinux_task_setuid(uid_t id0, uid_t id1, uid_t id2, int flags)
@@ -5552,8 +5550,8 @@ static struct security_operations selinux_ops = {
        .dentry_open =                  selinux_dentry_open,
 
        .task_create =                  selinux_task_create,
-       .task_alloc_security =          selinux_task_alloc_security,
-       .task_free_security =           selinux_task_free_security,
+       .cred_alloc_security =          selinux_cred_alloc_security,
+       .cred_free =                    selinux_cred_free,
        .task_setuid =                  selinux_task_setuid,
        .task_post_setuid =             selinux_task_post_setuid,
        .task_setgid =                  selinux_task_setgid,
@@ -5683,7 +5681,7 @@ static __init int selinux_init(void)
        printk(KERN_INFO "SELinux:  Initializing.\n");
 
        /* Set the security state for the initial task. */
-       if (task_alloc_security(current))
+       if (cred_alloc_security(current->cred))
                panic("SELinux:  Failed to initialize initial task.\n");
        tsec = current->cred->security;
        tsec->osid = tsec->sid = SECINITSID_KERNEL;
index 791da23..cc83731 100644 (file)
@@ -975,8 +975,8 @@ static int smack_file_receive(struct file *file)
  */
 
 /**
- * smack_task_alloc_security - "allocate" a task blob
- * @tsk: the task in need of a blob
+ * smack_cred_alloc_security - "allocate" a task cred blob
+ * @cred: the task creds in need of a blob
  *
  * Smack isn't using copies of blobs. Everyone
  * points to an immutable list. No alloc required.
@@ -984,24 +984,24 @@ static int smack_file_receive(struct file *file)
  *
  * Always returns 0
  */
-static int smack_task_alloc_security(struct task_struct *tsk)
+static int smack_cred_alloc_security(struct cred *cred)
 {
-       tsk->cred->security = current->cred->security;
+       cred->security = current->cred->security;
 
        return 0;
 }
 
 /**
- * smack_task_free_security - "free" a task blob
- * @task: the task with the blob
+ * smack_cred_free - "free" task-level security credentials
+ * @cred: the credentials in question
  *
  * Smack isn't using copies of blobs. Everyone
  * points to an immutable list. The blobs never go away.
  * There is no leak here.
  */
-static void smack_task_free_security(struct task_struct *task)
+static void smack_cred_free(struct cred *cred)
 {
-       task->cred->security = NULL;
+       cred->security = NULL;
 }
 
 /**
@@ -2630,8 +2630,8 @@ struct security_operations smack_ops = {
        .file_send_sigiotask =          smack_file_send_sigiotask,
        .file_receive =                 smack_file_receive,
 
-       .task_alloc_security =          smack_task_alloc_security,
-       .task_free_security =           smack_task_free_security,
+       .cred_alloc_security =          smack_cred_alloc_security,
+       .cred_free =                    smack_cred_free,
        .task_post_setuid =             cap_task_post_setuid,
        .task_setpgid =                 smack_task_setpgid,
        .task_getpgid =                 smack_task_getpgid,