aboutsummaryrefslogtreecommitdiff
path: root/security/keys/keyring.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/keys/keyring.c')
-rw-r--r--security/keys/keyring.c245
1 files changed, 148 insertions, 97 deletions
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index e2ab4f8e748..c9a5de19748 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -132,10 +132,17 @@ static int keyring_duplicate(struct key *keyring, const struct key *source)
(PAGE_SIZE - sizeof(*klist)) / sizeof(struct key);
ret = 0;
- sklist = source->payload.subscriptions;
- if (sklist && sklist->nkeys > 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;
@@ -148,6 +155,10 @@ static int keyring_duplicate(struct key *keyring, const struct key *source)
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,
@@ -157,7 +168,9 @@ static int keyring_duplicate(struct key *keyring, const struct key *source)
for (loop = klist->nkeys - 1; loop >= 0; loop--)
atomic_inc(&klist->keys[loop]->usage);
- keyring->payload.subscriptions = klist;
+ rcu_read_unlock();
+
+ rcu_assign_pointer(keyring->payload.subscriptions, klist);
ret = 0;
}
@@ -192,7 +205,7 @@ static void keyring_destroy(struct key *keyring)
write_unlock(&keyring_name_lock);
}
- klist = keyring->payload.subscriptions;
+ klist = rcu_dereference(keyring->payload.subscriptions);
if (klist) {
for (loop = klist->nkeys - 1; loop >= 0; loop--)
key_put(klist->keys[loop]);
@@ -216,17 +229,20 @@ static void keyring_describe(const struct key *keyring, struct seq_file *m)
seq_puts(m, "[anon]");
}
- klist = keyring->payload.subscriptions;
+ rcu_read_lock();
+ klist = rcu_dereference(keyring->payload.subscriptions);
if (klist)
seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys);
else
seq_puts(m, ": empty");
+ rcu_read_unlock();
} /* end keyring_describe() */
/*****************************************************************************/
/*
* read a list of key IDs from the keyring's contents
+ * - the keyring's semaphore is read-locked
*/
static long keyring_read(const struct key *keyring,
char __user *buffer, size_t buflen)
@@ -237,7 +253,7 @@ static long keyring_read(const struct key *keyring,
int loop, ret;
ret = 0;
- klist = keyring->payload.subscriptions;
+ klist = rcu_dereference(keyring->payload.subscriptions);
if (klist) {
/* calculate how much data we could return */
@@ -320,7 +336,7 @@ struct key *keyring_search_aux(struct key *keyring,
key_match_func_t match)
{
struct {
- struct key *keyring;
+ struct keyring_list *keylist;
int kix;
} stack[KEYRING_SEARCH_MAX_DEPTH];
@@ -328,10 +344,12 @@ struct key *keyring_search_aux(struct key *keyring,
struct timespec now;
struct key *key;
long err;
- int sp, psp, kix;
+ int sp, kix;
key_check(keyring);
+ rcu_read_lock();
+
/* top keyring must have search permission to begin the search */
key = ERR_PTR(-EACCES);
if (!key_permission(keyring, KEY_SEARCH))
@@ -347,11 +365,10 @@ struct key *keyring_search_aux(struct key *keyring,
/* start processing a new keyring */
descend:
- read_lock(&keyring->lock);
- if (keyring->flags & KEY_FLAG_REVOKED)
+ if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
goto not_this_keyring;
- keylist = keyring->payload.subscriptions;
+ keylist = rcu_dereference(keyring->payload.subscriptions);
if (!keylist)
goto not_this_keyring;
@@ -364,7 +381,7 @@ struct key *keyring_search_aux(struct key *keyring,
continue;
/* skip revoked keys and expired keys */
- if (key->flags & KEY_FLAG_REVOKED)
+ if (test_bit(KEY_FLAG_REVOKED, &key->flags))
continue;
if (key->expiry && now.tv_sec >= key->expiry)
@@ -379,7 +396,7 @@ struct key *keyring_search_aux(struct key *keyring,
continue;
/* we set a different error code if we find a negative key */
- if (key->flags & KEY_FLAG_NEGATIVE) {
+ if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
err = -ENOKEY;
continue;
}
@@ -390,48 +407,37 @@ struct key *keyring_search_aux(struct key *keyring,
/* search through the keyrings nested in this one */
kix = 0;
ascend:
- while (kix < keylist->nkeys) {
+ for (; kix < keylist->nkeys; kix++) {
key = keylist->keys[kix];
if (key->type != &key_type_keyring)
- goto next;
+ continue;
/* recursively search nested keyrings
* - only search keyrings for which we have search permission
*/
if (sp >= KEYRING_SEARCH_MAX_DEPTH)
- goto next;
+ continue;
if (!key_permission(key, KEY_SEARCH))
- goto next;
-
- /* evade loops in the keyring tree */
- for (psp = 0; psp < sp; psp++)
- if (stack[psp].keyring == keyring)
- goto next;
+ continue;
/* stack the current position */
- stack[sp].keyring = keyring;
+ stack[sp].keylist = keylist;
stack[sp].kix = kix;
sp++;
/* begin again with the new keyring */
keyring = key;
goto descend;
-
- next:
- kix++;
}
/* the keyring we're looking at was disqualified or didn't contain a
* matching key */
not_this_keyring:
- read_unlock(&keyring->lock);
-
if (sp > 0) {
/* resume the processing of a keyring higher up in the tree */
sp--;
- keyring = stack[sp].keyring;
- keylist = keyring->payload.subscriptions;
+ keylist = stack[sp].keylist;
kix = stack[sp].kix + 1;
goto ascend;
}
@@ -442,16 +448,9 @@ struct key *keyring_search_aux(struct key *keyring,
/* we found a viable match */
found:
atomic_inc(&key->usage);
- read_unlock(&keyring->lock);
-
- /* unwind the keyring stack */
- while (sp > 0) {
- sp--;
- read_unlock(&stack[sp].keyring->lock);
- }
-
key_check(key);
error:
+ rcu_read_unlock();
return key;
} /* end keyring_search_aux() */
@@ -489,7 +488,9 @@ struct key *__keyring_search_one(struct key *keyring,
struct key *key;
int loop;
- klist = keyring->payload.subscriptions;
+ rcu_read_lock();
+
+ klist = rcu_dereference(keyring->payload.subscriptions);
if (klist) {
for (loop = 0; loop < klist->nkeys; loop++) {
key = klist->keys[loop];
@@ -497,7 +498,7 @@ struct key *__keyring_search_one(struct key *keyring,
if (key->type == ktype &&
key->type->match(key, description) &&
key_permission(key, perm) &&
- !(key->flags & KEY_FLAG_REVOKED)
+ !test_bit(KEY_FLAG_REVOKED, &key->flags)
)
goto found;
}
@@ -509,6 +510,7 @@ struct key *__keyring_search_one(struct key *keyring,
found:
atomic_inc(&key->usage);
error:
+ rcu_read_unlock();
return key;
} /* end __keyring_search_one() */
@@ -540,7 +542,7 @@ struct key *find_keyring_by_name(const char *name, key_serial_t bound)
&keyring_name_hash[bucket],
type_data.link
) {
- if (keyring->flags & KEY_FLAG_REVOKED)
+ if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
continue;
if (strcmp(keyring->description, name) != 0)
@@ -579,7 +581,7 @@ struct key *find_keyring_by_name(const char *name, key_serial_t bound)
static int keyring_detect_cycle(struct key *A, struct key *B)
{
struct {
- struct key *subtree;
+ struct keyring_list *keylist;
int kix;
} stack[KEYRING_SEARCH_MAX_DEPTH];
@@ -587,20 +589,21 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
struct key *subtree, *key;
int sp, kix, ret;
+ rcu_read_lock();
+
ret = -EDEADLK;
if (A == B)
- goto error;
+ goto cycle_detected;
subtree = B;
sp = 0;
/* start processing a new keyring */
descend:
- read_lock(&subtree->lock);
- if (subtree->flags & KEY_FLAG_REVOKED)
+ if (test_bit(KEY_FLAG_REVOKED, &subtree->flags))
goto not_this_keyring;
- keylist = subtree->payload.subscriptions;
+ keylist = rcu_dereference(subtree->payload.subscriptions);
if (!keylist)
goto not_this_keyring;
kix = 0;
@@ -619,7 +622,7 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
goto too_deep;
/* stack the current position */
- stack[sp].subtree = subtree;
+ stack[sp].keylist = keylist;
stack[sp].kix = kix;
sp++;
@@ -632,13 +635,10 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
/* the keyring we're looking at was disqualified or didn't contain a
* matching key */
not_this_keyring:
- read_unlock(&subtree->lock);
-
if (sp > 0) {
/* resume the checking of a keyring higher up in the tree */
sp--;
- subtree = stack[sp].subtree;
- keylist = subtree->payload.subscriptions;
+ keylist = stack[sp].keylist;
kix = stack[sp].kix + 1;
goto ascend;
}
@@ -646,30 +646,36 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
ret = 0; /* no cycles detected */
error:
+ rcu_read_unlock();
return ret;
too_deep:
ret = -ELOOP;
- goto error_unwind;
+ goto error;
+
cycle_detected:
ret = -EDEADLK;
- error_unwind:
- read_unlock(&subtree->lock);
-
- /* unwind the keyring stack */
- while (sp > 0) {
- sp--;
- read_unlock(&stack[sp].subtree->lock);
- }
-
goto error;
} /* end keyring_detect_cycle() */
/*****************************************************************************/
/*
+ * dispose of a keyring list after the RCU grace period
+ */
+static void keyring_link_rcu_disposal(struct rcu_head *rcu)
+{
+ struct keyring_list *klist =
+ container_of(rcu, struct keyring_list, rcu);
+
+ kfree(klist);
+
+} /* end keyring_link_rcu_disposal() */
+
+/*****************************************************************************/
+/*
* link a key into to a keyring
- * - must be called with the keyring's semaphore held
+ * - must be called with the keyring's semaphore write-locked
*/
int __key_link(struct key *keyring, struct key *key)
{
@@ -679,7 +685,7 @@ int __key_link(struct key *keyring, struct key *key)
int ret;
ret = -EKEYREVOKED;
- if (keyring->flags & KEY_FLAG_REVOKED)
+ if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
goto error;
ret = -ENOTDIR;
@@ -710,9 +716,10 @@ int __key_link(struct key *keyring, struct key *key)
/* there's sufficient slack space to add directly */
atomic_inc(&key->usage);
- write_lock(&keyring->lock);
- klist->keys[klist->nkeys++] = key;
- write_unlock(&keyring->lock);
+ klist->keys[klist->nkeys] = key;
+ smp_wmb();
+ klist->nkeys++;
+ smp_wmb();
ret = 0;
}
@@ -723,6 +730,8 @@ int __key_link(struct key *keyring, struct key *key)
max += klist->maxkeys;
ret = -ENFILE;
+ if (max > 65535)
+ goto error3;
size = sizeof(*klist) + sizeof(*key) * max;
if (size > PAGE_SIZE)
goto error3;
@@ -743,14 +752,13 @@ int __key_link(struct key *keyring, struct key *key)
/* add the key into the new space */
atomic_inc(&key->usage);
-
- write_lock(&keyring->lock);
- keyring->payload.subscriptions = nklist;
nklist->keys[nklist->nkeys++] = key;
- write_unlock(&keyring->lock);
+
+ rcu_assign_pointer(keyring->payload.subscriptions, nklist);
/* dispose of the old keyring list */
- kfree(klist);
+ if (klist)
+ call_rcu(&klist->rcu, keyring_link_rcu_disposal);
ret = 0;
}
@@ -791,11 +799,26 @@ EXPORT_SYMBOL(key_link);
/*****************************************************************************/
/*
+ * dispose of a keyring list after the RCU grace period, freeing the unlinked
+ * key
+ */
+static void keyring_unlink_rcu_disposal(struct rcu_head *rcu)
+{
+ struct keyring_list *klist =
+ container_of(rcu, struct keyring_list, rcu);
+
+ key_put(klist->keys[klist->delkey]);
+ kfree(klist);
+
+} /* end keyring_unlink_rcu_disposal() */
+
+/*****************************************************************************/
+/*
* unlink the first link to a key from a keyring
*/
int key_unlink(struct key *keyring, struct key *key)
{
- struct keyring_list *klist;
+ struct keyring_list *klist, *nklist;
int loop, ret;
key_check(keyring);
@@ -819,31 +842,45 @@ int key_unlink(struct key *keyring, struct key *key)
ret = -ENOENT;
goto error;
- key_is_present:
+key_is_present:
+ /* we need to copy the key list for RCU purposes */
+ nklist = kmalloc(sizeof(*klist) + sizeof(*key) * klist->maxkeys,
+ GFP_KERNEL);
+ if (!nklist)
+ goto nomem;
+ nklist->maxkeys = klist->maxkeys;
+ nklist->nkeys = klist->nkeys - 1;
+
+ if (loop > 0)
+ memcpy(&nklist->keys[0],
+ &klist->keys[0],
+ loop * sizeof(klist->keys[0]));
+
+ if (loop < nklist->nkeys)
+ memcpy(&nklist->keys[loop],
+ &klist->keys[loop + 1],
+ (nklist->nkeys - loop) * sizeof(klist->keys[0]));
+
/* adjust the user's quota */
key_payload_reserve(keyring,
keyring->datalen - KEYQUOTA_LINK_BYTES);
- /* shuffle down the key pointers
- * - it might be worth shrinking the allocated memory, but that runs
- * the risk of ENOMEM as we would have to copy
- */
- write_lock(&keyring->lock);
+ rcu_assign_pointer(keyring->payload.subscriptions, nklist);
- klist->nkeys--;
- if (loop < klist->nkeys)
- memcpy(&klist->keys[loop],
- &klist->keys[loop + 1],
- (klist->nkeys - loop) * sizeof(struct key *));
+ up_write(&keyring->sem);
- write_unlock(&keyring->lock);
+ /* schedule for later cleanup */
+ klist->delkey = loop;
+ call_rcu(&klist->rcu, keyring_unlink_rcu_disposal);
- up_write(&keyring->sem);
- key_put(key);
ret = 0;
- error:
+error:
return ret;
+nomem:
+ ret = -ENOMEM;
+ up_write(&keyring->sem);
+ goto error;
} /* end key_unlink() */
@@ -851,13 +888,32 @@ EXPORT_SYMBOL(key_unlink);
/*****************************************************************************/
/*
+ * dispose of a keyring list after the RCU grace period, releasing the keys it
+ * links to
+ */
+static void keyring_clear_rcu_disposal(struct rcu_head *rcu)
+{
+ struct keyring_list *klist;
+ int loop;
+
+ klist = container_of(rcu, struct keyring_list, rcu);
+
+ for (loop = klist->nkeys - 1; loop >= 0; loop--)
+ key_put(klist->keys[loop]);
+
+ kfree(klist);
+
+} /* end keyring_clear_rcu_disposal() */
+
+/*****************************************************************************/
+/*
* clear the specified process keyring
* - implements keyctl(KEYCTL_CLEAR)
*/
int keyring_clear(struct key *keyring)
{
struct keyring_list *klist;
- int loop, ret;
+ int ret;
ret = -ENOTDIR;
if (keyring->type == &key_type_keyring) {
@@ -870,20 +926,15 @@ int keyring_clear(struct key *keyring)
key_payload_reserve(keyring,
sizeof(struct keyring_list));
- write_lock(&keyring->lock);
- keyring->payload.subscriptions = NULL;
- write_unlock(&keyring->lock);
+ rcu_assign_pointer(keyring->payload.subscriptions,
+ NULL);
}
up_write(&keyring->sem);
/* free the keys after the locks have been dropped */
- if (klist) {
- for (loop = klist->nkeys - 1; loop >= 0; loop--)
- key_put(klist->keys[loop]);
-
- kfree(klist);
- }
+ if (klist)
+ call_rcu(&klist->rcu, keyring_clear_rcu_disposal);
ret = 0;
}