(keyring)->payload.subscriptions, \
rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem)))
+#define rcu_deref_link_locked(klist, index, keyring) \
+ (rcu_dereference_protected( \
+ (klist)->keys[index], \
+ rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem)))
+
+#define MAX_KEYRING_LINKS \
+ min_t(size_t, USHRT_MAX - 1, \
+ ((PAGE_SIZE - sizeof(struct keyring_list)) / sizeof(struct key *)))
+
#define KEY_LINK_FIXQUOTA 1UL
/*
/*
* Clean up a keyring when it is destroyed. Unpublish its name if it had one
* and dispose of its data.
+ *
+ * The garbage collector detects the final key_put(), removes the keyring from
+ * the serial number tree and then does RCU synchronisation before coming here,
+ * so we shouldn't need to worry about code poking around here with the RCU
+ * readlock held by this time.
*/
static void keyring_destroy(struct key *keyring)
{
write_unlock(&keyring_name_lock);
}
- klist = rcu_dereference_check(keyring->payload.subscriptions,
- atomic_read(&keyring->usage) == 0);
+ klist = rcu_access_pointer(keyring->payload.subscriptions);
if (klist) {
for (loop = klist->nkeys - 1; loop >= 0; loop--)
- key_put(klist->keys[loop]);
+ key_put(rcu_access_pointer(klist->keys[loop]));
kfree(klist);
}
}
ret = -EFAULT;
for (loop = 0; loop < klist->nkeys; loop++) {
- key = klist->keys[loop];
+ key = rcu_deref_link_locked(klist, loop,
+ keyring);
tmp = sizeof(key_serial_t);
if (tmp > buflen)
bool no_state_check)
{
struct {
+ /* Need a separate keylist pointer for RCU purposes */
+ struct key *keyring;
struct keyring_list *keylist;
int kix;
} stack[KEYRING_SEARCH_MAX_DEPTH];
nkeys = keylist->nkeys;
smp_rmb();
for (kix = 0; kix < nkeys; kix++) {
- key = keylist->keys[kix];
+ key = rcu_dereference(keylist->keys[kix]);
kflags = key->flags;
/* ignore keys not of this type */
nkeys = keylist->nkeys;
smp_rmb();
for (; kix < nkeys; kix++) {
- key = keylist->keys[kix];
+ key = rcu_dereference(keylist->keys[kix]);
if (key->type != &key_type_keyring)
continue;
continue;
/* stack the current position */
+ stack[sp].keyring = keyring;
stack[sp].keylist = keylist;
stack[sp].kix = kix;
sp++;
if (sp > 0) {
/* resume the processing of a keyring higher up in the tree */
sp--;
+ keyring = stack[sp].keyring;
keylist = stack[sp].keylist;
kix = stack[sp].kix + 1;
goto ascend;
/* we found a viable match */
found:
atomic_inc(&key->usage);
+ key->last_used_at = now.tv_sec;
+ keyring->last_used_at = now.tv_sec;
+ while (sp > 0)
+ stack[--sp].keyring->last_used_at = now.tv_sec;
key_check(key);
key_ref = make_key_ref(key, possessed);
error_2:
nkeys = klist->nkeys;
smp_rmb();
for (loop = 0; loop < nkeys ; loop++) {
- key = klist->keys[loop];
-
+ key = rcu_dereference(klist->keys[loop]);
if (key->type == ktype &&
(!key->type->match ||
key->type->match(key, description)) &&
found:
atomic_inc(&key->usage);
+ keyring->last_used_at = key->last_used_at =
+ current_kernel_time().tv_sec;
rcu_read_unlock();
return make_key_ref(key, possessed);
}
* (ie. it has a zero usage count) */
if (!atomic_inc_not_zero(&keyring->usage))
continue;
+ keyring->last_used_at = current_kernel_time().tv_sec;
goto out;
}
}
nkeys = keylist->nkeys;
smp_rmb();
for (; kix < nkeys; kix++) {
- key = keylist->keys[kix];
+ key = rcu_dereference(keylist->keys[kix]);
if (key == A)
goto cycle_detected;
container_of(rcu, struct keyring_list, rcu);
if (klist->delkey != USHRT_MAX)
- key_put(klist->keys[klist->delkey]);
+ key_put(rcu_access_pointer(klist->keys[klist->delkey]));
kfree(klist);
}
struct keyring_list *klist, *nklist;
unsigned long prealloc;
unsigned max;
+ time_t lowest_lru;
size_t size;
- int loop, ret;
+ int loop, lru, ret;
kenter("%d,%s,%s,", key_serial(keyring), type->name, description);
klist = rcu_dereference_locked_keyring(keyring);
/* see if there's a matching key we can displace */
+ lru = -1;
if (klist && klist->nkeys > 0) {
+ lowest_lru = TIME_T_MAX;
for (loop = klist->nkeys - 1; loop >= 0; loop--) {
- if (klist->keys[loop]->type == type &&
- strcmp(klist->keys[loop]->description,
- description) == 0
- ) {
- /* found a match - we'll replace this one with
- * the new key */
- size = sizeof(struct key *) * klist->maxkeys;
- size += sizeof(*klist);
- BUG_ON(size > PAGE_SIZE);
-
- ret = -ENOMEM;
- nklist = kmemdup(klist, size, GFP_KERNEL);
- if (!nklist)
- goto error_sem;
-
- /* note replacement slot */
- klist->delkey = nklist->delkey = loop;
- prealloc = (unsigned long)nklist;
+ struct key *key = rcu_deref_link_locked(klist, loop,
+ keyring);
+ if (key->type == type &&
+ strcmp(key->description, description) == 0) {
+ /* Found a match - we'll replace the link with
+ * one to the new key. We record the slot
+ * position.
+ */
+ klist->delkey = loop;
+ prealloc = 0;
goto done;
}
+ if (key->last_used_at < lowest_lru) {
+ lowest_lru = key->last_used_at;
+ lru = loop;
+ }
}
}
+ /* If the keyring is full then do an LRU discard */
+ if (klist &&
+ klist->nkeys == klist->maxkeys &&
+ klist->maxkeys >= MAX_KEYRING_LINKS) {
+ kdebug("LRU discard %d\n", lru);
+ klist->delkey = lru;
+ prealloc = 0;
+ goto done;
+ }
+
/* check that we aren't going to overrun the user's quota */
ret = key_payload_reserve(keyring,
keyring->datalen + KEYQUOTA_LINK_BYTES);
if (klist && klist->nkeys < klist->maxkeys) {
/* there's sufficient slack space to append directly */
- nklist = NULL;
+ klist->delkey = klist->nkeys;
prealloc = KEY_LINK_FIXQUOTA;
} else {
/* grow the key list */
max = 4;
- if (klist)
+ if (klist) {
max += klist->maxkeys;
+ if (max > MAX_KEYRING_LINKS)
+ max = MAX_KEYRING_LINKS;
+ BUG_ON(max <= klist->maxkeys);
+ }
- ret = -ENFILE;
- if (max > USHRT_MAX - 1)
- goto error_quota;
size = sizeof(*klist) + sizeof(struct key *) * max;
- if (size > PAGE_SIZE)
- goto error_quota;
ret = -ENOMEM;
nklist = kmalloc(size, GFP_KERNEL);
}
/* add the key into the new space */
- nklist->keys[nklist->delkey] = NULL;
+ RCU_INIT_POINTER(nklist->keys[nklist->delkey], NULL);
+ prealloc = (unsigned long)nklist | KEY_LINK_FIXQUOTA;
}
- prealloc = (unsigned long)nklist | KEY_LINK_FIXQUOTA;
done:
*_prealloc = prealloc;
kleave(" = 0");
unsigned long *_prealloc)
{
struct keyring_list *klist, *nklist;
+ struct key *discard;
nklist = (struct keyring_list *)(*_prealloc & ~KEY_LINK_FIXQUOTA);
*_prealloc = 0;
klist = rcu_dereference_locked_keyring(keyring);
atomic_inc(&key->usage);
+ keyring->last_used_at = key->last_used_at =
+ current_kernel_time().tv_sec;
/* there's a matching key we can displace or an empty slot in a newly
* allocated list we can fill */
if (nklist) {
- kdebug("replace %hu/%hu/%hu",
+ kdebug("reissue %hu/%hu/%hu",
nklist->delkey, nklist->nkeys, nklist->maxkeys);
- nklist->keys[nklist->delkey] = key;
+ RCU_INIT_POINTER(nklist->keys[nklist->delkey], key);
rcu_assign_pointer(keyring->payload.subscriptions, nklist);
klist->delkey, klist->nkeys, klist->maxkeys);
call_rcu(&klist->rcu, keyring_unlink_rcu_disposal);
}
+ } else if (klist->delkey < klist->nkeys) {
+ kdebug("replace %hu/%hu/%hu",
+ klist->delkey, klist->nkeys, klist->maxkeys);
+
+ discard = rcu_dereference_protected(
+ klist->keys[klist->delkey],
+ rwsem_is_locked(&keyring->sem));
+ rcu_assign_pointer(klist->keys[klist->delkey], key);
+ /* The garbage collector will take care of RCU
+ * synchronisation */
+ key_put(discard);
} else {
/* there's sufficient slack space to append directly */
- klist->keys[klist->nkeys] = key;
+ kdebug("append %hu/%hu/%hu",
+ klist->delkey, klist->nkeys, klist->maxkeys);
+
+ RCU_INIT_POINTER(klist->keys[klist->delkey], key);
smp_wmb();
klist->nkeys++;
}
if (klist) {
/* search the keyring for the key */
for (loop = 0; loop < klist->nkeys; loop++)
- if (klist->keys[loop] == key)
+ if (rcu_access_pointer(klist->keys[loop]) == key)
goto key_is_present;
}
klist = container_of(rcu, struct keyring_list, rcu);
for (loop = klist->nkeys - 1; loop >= 0; loop--)
- key_put(klist->keys[loop]);
+ key_put(rcu_access_pointer(klist->keys[loop]));
kfree(klist);
}
/* work out how many subscriptions we're keeping */
keep = 0;
for (loop = klist->nkeys - 1; loop >= 0; loop--)
- if (!key_is_dead(klist->keys[loop], limit))
+ if (!key_is_dead(rcu_deref_link_locked(klist, loop, keyring),
+ limit))
keep++;
if (keep == klist->nkeys)
*/
keep = 0;
for (loop = klist->nkeys - 1; loop >= 0; loop--) {
- key = klist->keys[loop];
+ key = rcu_deref_link_locked(klist, loop, keyring);
if (!key_is_dead(key, limit)) {
if (keep >= max)
goto discard_new;
- new->keys[keep++] = key_get(key);
+ RCU_INIT_POINTER(new->keys[keep++], key_get(key));
}
}
new->nkeys = keep;