Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

KEYS: Do LRU discard in full keyrings

Do an LRU discard in keyrings that are full rather than returning ENFILE. To
perform this, a time_t is added to the key struct and updated by the creation
of a link to a key and by a key being found as the result of a search. At the
completion of a successful search, the keyrings in the path between the root of
the search and the first found link to it also have their last-used times
updated.

Note that discarding a link to a key from a keyring does not necessarily
destroy the key as there may be references held by other places.

An alternate discard method that might suffice is to perform FIFO discard from
the keyring, using the spare 2-byte hole in the keylist header as the index of
the next link to be discarded.

This is useful when using a keyring as a cache for DNS results or foreign
filesystem IDs.


This can be tested by the following. As root do:

echo 1000 >/proc/sys/kernel/keys/root_maxkeys

kr=`keyctl newring foo @s`
for ((i=0; i<2000; i++)); do keyctl add user a$i a $kr; done

Without this patch ENFILE should be reported when the keyring fills up. With
this patch, the keyring discards keys in an LRU fashion. Note that the stored
LRU time has a granularity of 1s.

After doing this, /proc/key-users can be observed and should show that most of
the 2000 keys have been discarded:

[root@andromeda ~]# cat /proc/key-users
0: 517 516/516 513/1000 5249/20000

The "513/1000" here is the number of quota-accounted keys present for this user
out of the maximum permitted.

In /proc/keys, the keyring shows the number of keys it has and the number of
slots it has allocated:

[root@andromeda ~]# grep foo /proc/keys
200c64c4 I--Q-- 1 perm 3b3f0000 0 0 keyring foo: 509/509

The maximum is (PAGE_SIZE - header) / key pointer size. That's typically 509
on a 64-bit system and 1020 on a 32-bit system.

Signed-off-by: David Howells <dhowells@redhat.com>

+43 -7
+1
include/linux/key.h
··· 136 136 time_t expiry; /* time at which key expires (or 0) */ 137 137 time_t revoked_at; /* time at which key was revoked */ 138 138 }; 139 + time_t last_used_at; /* last time used for LRU keyring discard */ 139 140 uid_t uid; 140 141 gid_t gid; 141 142 key_perm_t perm; /* access permissions */
+40 -7
security/keys/keyring.c
··· 30 30 (klist)->keys[index], \ 31 31 rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem))) 32 32 33 + #define MAX_KEYRING_LINKS \ 34 + min_t(size_t, USHRT_MAX - 1, \ 35 + ((PAGE_SIZE - sizeof(struct keyring_list)) / sizeof(struct key *))) 36 + 33 37 #define KEY_LINK_FIXQUOTA 1UL 34 38 35 39 /* ··· 323 319 bool no_state_check) 324 320 { 325 321 struct { 322 + /* Need a separate keylist pointer for RCU purposes */ 323 + struct key *keyring; 326 324 struct keyring_list *keylist; 327 325 int kix; 328 326 } stack[KEYRING_SEARCH_MAX_DEPTH]; ··· 457 451 continue; 458 452 459 453 /* stack the current position */ 454 + stack[sp].keyring = keyring; 460 455 stack[sp].keylist = keylist; 461 456 stack[sp].kix = kix; 462 457 sp++; ··· 473 466 if (sp > 0) { 474 467 /* resume the processing of a keyring higher up in the tree */ 475 468 sp--; 469 + keyring = stack[sp].keyring; 476 470 keylist = stack[sp].keylist; 477 471 kix = stack[sp].kix + 1; 478 472 goto ascend; ··· 485 477 /* we found a viable match */ 486 478 found: 487 479 atomic_inc(&key->usage); 480 + key->last_used_at = now.tv_sec; 481 + keyring->last_used_at = now.tv_sec; 482 + while (sp > 0) 483 + stack[--sp].keyring->last_used_at = now.tv_sec; 488 484 key_check(key); 489 485 key_ref = make_key_ref(key, possessed); 490 486 error_2: ··· 570 558 571 559 found: 572 560 atomic_inc(&key->usage); 561 + keyring->last_used_at = key->last_used_at = 562 + current_kernel_time().tv_sec; 573 563 rcu_read_unlock(); 574 564 return make_key_ref(key, possessed); 575 565 } ··· 625 611 * (ie. it has a zero usage count) */ 626 612 if (!atomic_inc_not_zero(&keyring->usage)) 627 613 continue; 614 + keyring->last_used_at = current_kernel_time().tv_sec; 628 615 goto out; 629 616 } 630 617 } ··· 749 734 struct keyring_list *klist, *nklist; 750 735 unsigned long prealloc; 751 736 unsigned max; 737 + time_t lowest_lru; 752 738 size_t size; 753 - int loop, ret; 739 + int loop, lru, ret; 754 740 755 741 kenter("%d,%s,%s,", key_serial(keyring), type->name, description); 756 742 ··· 772 756 klist = rcu_dereference_locked_keyring(keyring); 773 757 774 758 /* see if there's a matching key we can displace */ 759 + lru = -1; 775 760 if (klist && klist->nkeys > 0) { 761 + lowest_lru = TIME_T_MAX; 776 762 for (loop = klist->nkeys - 1; loop >= 0; loop--) { 777 763 struct key *key = rcu_deref_link_locked(klist, loop, 778 764 keyring); ··· 788 770 prealloc = 0; 789 771 goto done; 790 772 } 773 + if (key->last_used_at < lowest_lru) { 774 + lowest_lru = key->last_used_at; 775 + lru = loop; 776 + } 791 777 } 778 + } 779 + 780 + /* If the keyring is full then do an LRU discard */ 781 + if (klist && 782 + klist->nkeys == klist->maxkeys && 783 + klist->maxkeys >= MAX_KEYRING_LINKS) { 784 + kdebug("LRU discard %d\n", lru); 785 + klist->delkey = lru; 786 + prealloc = 0; 787 + goto done; 792 788 } 793 789 794 790 /* check that we aren't going to overrun the user's quota */ ··· 818 786 } else { 819 787 /* grow the key list */ 820 788 max = 4; 821 - if (klist) 789 + if (klist) { 822 790 max += klist->maxkeys; 791 + if (max > MAX_KEYRING_LINKS) 792 + max = MAX_KEYRING_LINKS; 793 + BUG_ON(max <= klist->maxkeys); 794 + } 823 795 824 - ret = -ENFILE; 825 - if (max > USHRT_MAX - 1) 826 - goto error_quota; 827 796 size = sizeof(*klist) + sizeof(struct key *) * max; 828 - if (size > PAGE_SIZE) 829 - goto error_quota; 830 797 831 798 ret = -ENOMEM; 832 799 nklist = kmalloc(size, GFP_KERNEL); ··· 904 873 klist = rcu_dereference_locked_keyring(keyring); 905 874 906 875 atomic_inc(&key->usage); 876 + keyring->last_used_at = key->last_used_at = 877 + current_kernel_time().tv_sec; 907 878 908 879 /* there's a matching key we can displace or an empty slot in a newly 909 880 * allocated list we can fill */
+2
security/keys/process_keys.c
··· 732 732 if (ret < 0) 733 733 goto invalid_key; 734 734 735 + key->last_used_at = current_kernel_time().tv_sec; 736 + 735 737 error: 736 738 put_cred(cred); 737 739 return key_ref;