/* keyring.c: keyring handling
*
* Copyright (C) 2004 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/slab.h>
#include <linux/seq_file.h>
#include <linux/err.h>
#include <asm/uaccess.h>
#include "internal.h"
/*
* when plumbing the depths of the key tree, this sets a hard limit set on how
* deep we're willing to go
*/
#define KEYRING_SEARCH_MAX_DEPTH 6
/*
* we keep all named keyrings in a hash to speed looking them up
*/
#define KEYRING_NAME_HASH_SIZE (1 << 5)
static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE];
static DEFINE_RWLOCK(keyring_name_lock);
static inline unsigned keyring_hash(const char *desc)
{
unsigned bucket = 0;
for (; *desc; desc++)
bucket += (unsigned char) *desc;
return bucket & (KEYRING_NAME_HASH_SIZE - 1);
}
/*
* the keyring type definition
*/
static int keyring_instantiate(struct key *keyring,
const void *data, size_t datalen);
static int keyring_duplicate(struct key *keyring, const struct key *source);
static int keyring_match(const struct key *keyring, const void *criterion);
static void keyring_destroy(struct key *keyring);
static void keyring_describe(const struct key *keyring, struct seq_file *m);
static long keyring_read(const struct key *keyring,
char __user *buffer, size_t buflen);
struct key_type key_type_keyring = {
.name = "keyring",
.def_datalen = sizeof(struct keyring_list),
.instantiate = keyring_instantiate,
.duplicate = keyring_duplicate,
.match = keyring_match,
.destroy = keyring_destroy,
.describe = keyring_describe,
.read = keyring_read,
};
/*
* semaphore to serialise link/link calls to prevent two link calls in parallel
* introducing a cycle
*/
DECLARE_RWSEM(keyring_serialise_link_sem);
/*****************************************************************************/
/*
* publish the name of a keyring so that it can be found by name (if it has
* one)
*/
void keyring_publish_name(struct key *keyring)
{
int bucket;
if (keyring->description) {
bucket = keyring_hash(keyring->description);
write_lock(&keyring_name_lock);
if (!keyring_name_hash[bucket].next)
INIT_LIST_HEAD(&keyring_name_hash[bucket]);
list_add_tail(&keyring->type_data.link,
&keyring_name_hash[bucket]);
write_unlock(&keyring_name_lock);
}
} /* end keyring_publish_name() */
/*****************************************************************************/
/*
* initialise a keyring
* - we object if we were given any data
*/
static int keyring_instantiate(struct key *keyring,
const void *data, size_t datalen)
{
int ret;
ret = -EINVAL;
if (datalen == 0) {
/* make the keyring available by name if it has one */
keyring_publish_name(keyring);
ret = 0;
}
return ret;
} /* end keyring_instantiate() */
/*****************************************************************************/
/*
* duplicate the list of subscribed keys from a source keyring into this one
*/
static int keyring_duplicate(struct key *keyring, const struct key *source)
{
struct keyring_list *sklist, *klist;
unsigned max;
size_t size;
int loop, ret;
const unsigned limit =
(PAGE_SIZE - sizeof(*klist)) / sizeof(struct key);
ret = 0;
/* find out how many keys are currently linked */
rcu_read_lock();
sklist = rcu_dereference(source->payload.subscriptions);
max = 0;
if (sklist)
max = sklist->nkeys;
rcu_read_unlock();
/* allocate a new payload and stuff load with key links */
if (max > 0) {
BUG_ON(max > limit);
max = (max + 3) & ~3;
if (max > limit)
max = limit;
ret = -ENOMEM;
size = sizeof(*klist) + sizeof(struct key) * max;
klist = kmalloc(size, GFP_KERNEL);
if (!klist)
goto error;
/* set links */
rcu_read_lock();
sklist = rcu_dereference(source->payload.subscriptions);
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);