summaryrefslogtreecommitdiffstats
path: root/security/keys
diff options
context:
space:
mode:
Diffstat (limited to 'security/keys')
-rw-r--r--security/keys/compat.c3
-rw-r--r--security/keys/gc.c1
-rw-r--r--security/keys/internal.h1
-rw-r--r--security/keys/keyctl.c102
-rw-r--r--security/keys/process_keys.c49
5 files changed, 156 insertions, 0 deletions
diff --git a/security/keys/compat.c b/security/keys/compat.c
index c766c68a63bc..792c0a611a6d 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -82,6 +82,9 @@ asmlinkage long compat_sys_keyctl(u32 option,
case KEYCTL_GET_SECURITY:
return keyctl_get_security(arg2, compat_ptr(arg3), arg4);
+ case KEYCTL_SESSION_TO_PARENT:
+ return keyctl_session_to_parent();
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 44adc325e15c..1e616aef55fd 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -65,6 +65,7 @@ static void key_gc_timer_func(unsigned long data)
* - return true if we altered the keyring
*/
static bool key_gc_keyring(struct key *keyring, time_t limit)
+ __releases(key_serial_lock)
{
struct keyring_list *klist;
struct key *key;
diff --git a/security/keys/internal.h b/security/keys/internal.h
index fb830514c337..24ba0307b7ad 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -201,6 +201,7 @@ extern long keyctl_set_timeout(key_serial_t, unsigned);
extern long keyctl_assume_authority(key_serial_t);
extern long keyctl_get_security(key_serial_t keyid, char __user *buffer,
size_t buflen);
+extern long keyctl_session_to_parent(void);
/*
* debugging key validation
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 736d7800f97f..74c968524592 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -1228,6 +1228,105 @@ long keyctl_get_security(key_serial_t keyid,
return ret;
}
+/*
+ * attempt to install the calling process's session keyring on the process's
+ * parent process
+ * - the keyring must exist and must grant us LINK permission
+ * - implements keyctl(KEYCTL_SESSION_TO_PARENT)
+ */
+long keyctl_session_to_parent(void)
+{
+ struct task_struct *me, *parent;
+ const struct cred *mycred, *pcred;
+ struct cred *cred, *oldcred;
+ key_ref_t keyring_r;
+ int ret;
+
+ keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_LINK);
+ if (IS_ERR(keyring_r))
+ return PTR_ERR(keyring_r);
+
+ /* our parent is going to need a new cred struct, a new tgcred struct
+ * and new security data, so we allocate them here to prevent ENOMEM in
+ * our parent */
+ ret = -ENOMEM;
+ cred = cred_alloc_blank();
+ if (!cred)
+ goto error_keyring;
+
+ cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r);
+ keyring_r = NULL;
+
+ me = current;
+ write_lock_irq(&tasklist_lock);
+
+ parent = me->real_parent;
+ ret = -EPERM;
+
+ /* the parent mustn't be init and mustn't be a kernel thread */
+ if (parent->pid <= 1 || !parent->mm)
+ goto not_permitted;
+
+ /* the parent must be single threaded */
+ if (atomic_read(&parent->signal->count) != 1)
+ goto not_permitted;
+
+ /* the parent and the child must have different session keyrings or
+ * there's no point */
+ mycred = current_cred();
+ pcred = __task_cred(parent);
+ if (mycred == pcred ||
+ mycred->tgcred->session_keyring == pcred->tgcred->session_keyring)
+ goto already_same;
+
+ /* the parent must have the same effective ownership and mustn't be
+ * SUID/SGID */
+ if (pcred-> uid != mycred->euid ||
+ pcred->euid != mycred->euid ||
+ pcred->suid != mycred->euid ||
+ pcred-> gid != mycred->egid ||
+ pcred->egid != mycred->egid ||
+ pcred->sgid != mycred->egid)
+ goto not_permitted;
+
+ /* the keyrings must have the same UID */
+ if (pcred ->tgcred->session_keyring->uid != mycred->euid ||
+ mycred->tgcred->session_keyring->uid != mycred->euid)
+ goto not_permitted;
+
+ /* the LSM must permit the replacement of the parent's keyring with the
+ * keyring from this process */
+ ret = security_key_session_to_parent(mycred, pcred,
+ key_ref_to_ptr(keyring_r));
+ if (ret < 0)
+ goto not_permitted;
+
+ /* if there's an already pending keyring replacement, then we replace
+ * that */
+ oldcred = parent->replacement_session_keyring;
+
+ /* the replacement session keyring is applied just prior to userspace
+ * restarting */
+ parent->replacement_session_keyring = cred;
+ cred = NULL;
+ set_ti_thread_flag(task_thread_info(parent), TIF_NOTIFY_RESUME);
+
+ write_unlock_irq(&tasklist_lock);
+ if (oldcred)
+ put_cred(oldcred);
+ return 0;
+
+already_same:
+ ret = 0;
+not_permitted:
+ put_cred(cred);
+ return ret;
+
+error_keyring:
+ key_ref_put(keyring_r);
+ return ret;
+}
+
/*****************************************************************************/
/*
* the key control system call
@@ -1313,6 +1412,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
(char __user *) arg3,
(size_t) arg4);
+ case KEYCTL_SESSION_TO_PARENT:
+ return keyctl_session_to_parent();
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 4739cfbb41b7..5c23afb31ece 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -17,6 +17,7 @@
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/mutex.h>
+#include <linux/security.h>
#include <linux/user_namespace.h>
#include <asm/uaccess.h>
#include "internal.h"
@@ -768,3 +769,51 @@ error:
abort_creds(new);
return ret;
}
+
+/*
+ * Replace a process's session keyring when that process resumes userspace on
+ * behalf of one of its children
+ */
+void key_replace_session_keyring(void)
+{
+ const struct cred *old;
+ struct cred *new;
+
+ if (!current->replacement_session_keyring)
+ return;
+
+ write_lock_irq(&tasklist_lock);
+ new = current->replacement_session_keyring;
+ current->replacement_session_keyring = NULL;
+ write_unlock_irq(&tasklist_lock);
+
+ if (!new)
+ return;
+
+ old = current_cred();
+ new-> uid = old-> uid;
+ new-> euid = old-> euid;
+ new-> suid = old-> suid;
+ new->fsuid = old->fsuid;
+ new-> gid = old-> gid;
+ new-> egid = old-> egid;
+ new-> sgid = old-> sgid;
+ new->fsgid = old->fsgid;
+ new->user = get_uid(old->user);
+ new->group_info = get_group_info(old->group_info);
+
+ new->securebits = old->securebits;
+ new->cap_inheritable = old->cap_inheritable;
+ new->cap_permitted = old->cap_permitted;
+ new->cap_effective = old->cap_effective;
+ new->cap_bset = old->cap_bset;
+
+ new->jit_keyring = old->jit_keyring;
+ new->thread_keyring = key_get(old->thread_keyring);
+ new->tgcred->tgid = old->tgcred->tgid;
+ new->tgcred->process_keyring = key_get(old->tgcred->process_keyring);
+
+ security_transfer_creds(new, old);
+
+ commit_creds(new);
+}
OpenPOWER on IntegriCloud