/* Management of a process's keyrings * * Copyright (C) 2004-2005, 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 License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */#include <linux/module.h>#include <linux/init.h>#include <linux/sched.h>#include <linux/keyctl.h>#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"/* session keyring create vs join semaphore */staticDEFINE_MUTEX(key_session_mutex);/* user keyring creation semaphore */staticDEFINE_MUTEX(key_user_keyring_mutex);/* the root user's tracking struct */struct key_user root_key_user = {.usage =ATOMIC_INIT(3),.cons_lock =__MUTEX_INITIALIZER(root_key_user.cons_lock),.lock =__SPIN_LOCK_UNLOCKED(root_key_user.lock),.nkeys =ATOMIC_INIT(2),.nikeys =ATOMIC_INIT(2),.uid =0,.user_ns = &init_user_ns,};/*****************************************************************************//* * install user and user session keyrings for a particular UID */intinstall_user_keyrings(void){struct user_struct *user;const struct cred *cred;struct key *uid_keyring, *session_keyring;char buf[20];int ret;
cred =current_cred();
user = cred->user;kenter("%p{%u}", user, user->uid);if(user->uid_keyring) {kleave(" = 0 [exist]");return0;}mutex_lock(&key_user_keyring_mutex);
ret =0;if(!user->uid_keyring) {/* get the UID-specific keyring * - there may be one in existence already as it may have been * pinned by a session, but the user_struct pointing to it * may have been destroyed by setuid */sprintf(buf,"_uid.%u", user->uid);
uid_keyring =find_keyring_by_name(buf,true);if(IS_ERR(uid_keyring)) {
uid_keyring =keyring_alloc(buf, user->uid, (gid_t) -1,
cred, KEY_ALLOC_IN_QUOTA,
NULL);if(IS_ERR(uid_keyring)) {
ret =PTR_ERR(uid_keyring);goto error;}}/* get a default session keyring (which might also exist * already) */sprintf(buf,"_uid_ses.%u", user->uid);
session_keyring =find_keyring_by_name(buf,true);if(IS_ERR(session_keyring)) {
session_keyring =keyring_alloc(buf, user->uid, (gid_t) -1,
cred, KEY_ALLOC_IN_QUOTA, NULL);if(IS_ERR(session_keyring)) {
ret =PTR_ERR(session_keyring);goto error_release;}/* we install a link from the user session keyring to * the user keyring */
ret =key_link(session_keyring, uid_keyring);if(ret <0)goto error_release_both;}/* install the keyrings */
user->uid_keyring = uid_keyring;
user->session_keyring = session_keyring;}mutex_unlock(&key_user_keyring_mutex);kleave(" = 0");return0;
error_release_both:key_put(session_keyring);
error_release:key_put(uid_keyring);
error:mutex_unlock(&key_user_keyring_mutex);kleave(" =%d", ret);return ret;}/* * install a fresh thread keyring directly to new credentials */intinstall_thread_keyring_to_cred(struct cred *new){struct key *keyring;
keyring =keyring_alloc("_tid",new->uid,new->gid,new,
KEY_ALLOC_QUOTA_OVERRUN, NULL);if(IS_ERR(keyring))returnPTR_ERR(keyring);new->thread_keyring = keyring;return0;}/* * install a fresh thread keyring, discarding the old one */static intinstall_thread_keyring(void){struct cred *new;int ret;new=prepare_creds();if(!new)return-ENOMEM;BUG_ON(new->thread_keyring);
ret =install_thread_keyring_to_cred(new);if(ret <0) {abort_creds(new);return ret;}returncommit_creds(new);}/* * install a process keyring directly to a credentials struct * - returns -EEXIST if there was already a process keyring, 0 if one installed, * and other -ve on any other error */intinstall_process_keyring_to_cred(struct cred *new){struct key *keyring;int ret;if(new->tgcred->process_keyring)return-EEXIST;
keyring =keyring_alloc("_pid",new->uid,new->gid,new, KEY_ALLOC_QUOTA_OVERRUN, NULL);if(IS_ERR(keyring))returnPTR_ERR(keyring);spin_lock_irq(&new->tgcred->lock);if(!new->tgcred->process_keyring) {new->tgcred->process_keyring = keyring;
keyring = NULL;
ret =0;}else{
ret = -EEXIST;}spin_unlock_irq(&new->tgcred->lock);key_put(keyring);return ret;}/* * make sure a process keyring is installed * - we */static intinstall_process_keyring(void){struct cred *new;int ret;new=prepare_creds();if(!new)return-ENOMEM;
ret =install_process_keyring_to_cred(new);if(ret <0) {abort_creds(new);return ret != -EEXIST ? ret :0;}returncommit_creds(new);}/* * install a session keyring directly to a credentials struct */intinstall_session_keyring_to_cred(struct cred *cred,struct key *keyring){unsigned long flags;struct key *old;might_sleep();/* create an empty session keyring */if(!keyring) {
flags = KEY_ALLOC_QUOTA_OVERRUN;if(cred->tgcred->session_keyring)
flags = KEY_ALLOC_IN_QUOTA;
keyring =keyring_alloc("_ses", cred->uid, cred->gid,
cred, flags, NULL);if(IS_ERR(keyring))returnPTR_ERR(keyring);}else{atomic_inc(&keyring->usage);}/* install the keyring */spin_lock_irq(&cred->tgcred->lock);
old = cred->tgcred->session_keyring;rcu_assign_pointer(cred->tgcred->session_keyring, keyring);spin_unlock_irq(&cred->tgcred->lock);/* we're using RCU on the pointer, but there's no point synchronising * on it if it didn't previously point to anything */if(old) {synchronize_rcu();key_put(old);}return0;}/* * install a session keyring, discarding the old one * - if a keyring is not supplied, an empty one is invented */static intinstall_session_keyring(struct key *keyring){struct cred *new;int ret;new=prepare_creds();if(!new)return-ENOMEM;
ret =install_session_keyring_to_cred(new, NULL);if(ret <0) {abort_creds(new);return ret;}returncommit_creds(new);}/*****************************************************************************//* * the filesystem user ID changed */voidkey_fsuid_changed(struct task_struct *tsk){/* update the ownership of the thread keyring */BUG_ON(!tsk->cred);if(tsk->cred->thread_keyring) {down_write(&tsk->cred->thread_keyring->sem);
tsk->cred->thread_keyring->uid = tsk->cred->fsuid;up_write(&tsk->cred->thread_keyring->