summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/linux/key.h1
-rw-r--r--security/keys/keyring.c47
-rw-r--r--security/keys/process_keys.c2
3 files changed, 43 insertions, 7 deletions
diff --git a/include/linux/key.h b/include/linux/key.h
index c505f83c9691..13c0dcd8ee48 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -136,6 +136,7 @@ struct key {
time_t expiry; /* time at which key expires (or 0) */
time_t revoked_at; /* time at which key was revoked */
};
+ time_t last_used_at; /* last time used for LRU keyring discard */
uid_t uid;
gid_t gid;
key_perm_t perm; /* access permissions */
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 459b3cc347f2..89d02cfb00c2 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -30,6 +30,10 @@
(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
/*
@@ -319,6 +323,8 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref,
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];
@@ -451,6 +457,7 @@ ascend:
continue;
/* stack the current position */
+ stack[sp].keyring = keyring;
stack[sp].keylist = keylist;
stack[sp].kix = kix;
sp++;
@@ -466,6 +473,7 @@ not_this_keyring:
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;
@@ -477,6 +485,10 @@ not_this_keyring:
/* 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:
@@ -558,6 +570,8 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref,
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);
}
@@ -611,6 +625,7 @@ struct key *find_keyring_by_name(const char *name, bool skip_perm_check)
* (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;
}
}
@@ -734,8 +749,9 @@ int __key_link_begin(struct key *keyring, const struct key_type *type,
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);
@@ -756,7 +772,9 @@ int __key_link_begin(struct key *keyring, const struct key_type *type,
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--) {
struct key *key = rcu_deref_link_locked(klist, loop,
keyring);
@@ -770,9 +788,23 @@ int __key_link_begin(struct key *keyring, const struct key_type *type,
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);
@@ -786,15 +818,14 @@ int __key_link_begin(struct key *keyring, const struct key_type *type,
} 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);
@@ -873,6 +904,8 @@ void __key_link(struct key *keyring, struct key *key,
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 */
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index be7ecb2018dd..e137fcd7042c 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -732,6 +732,8 @@ try_again:
if (ret < 0)
goto invalid_key;
+ key->last_used_at = current_kernel_time().tv_sec;
+
error:
put_cred(cred);
return key_ref;