From: Amerigo Wang <amwang@redhat.com> Date: Sat, 20 Feb 2010 10:00:26 -0500 Subject: [misc] make the keyring quotas controllable via /proc/sys Message-id: <20100220100359.4595.12169.sendpatchset@localhost.localdomain> Patchwork-id: 23368 O-Subject: [v2 PATCH RHEL5] KEYS: Make the keyring quotas controllable through /proc/sys Bugzilla: 441243 RH-Acked-by: Jon Masters <jcm@redhat.com> RH-Acked-by: David Howells <dhowells@redhat.com> BZ: https://bugzilla.redhat.com/show_bug.cgi?id=441243 Description: The problem is caused by the value for KEYQUOTA_MAX_KEYS (100) in the kernel source. Kernel keyring quotas are too restricted for the customer's environment. The limit of 100 rings for a single user is too small for their usecase; OpenAFS is the solution that hits this limit, but every other application that does so would have the very same problem. KABI: Use __GENKSYMS__ to avoid the breakage. Brew: https://brewweb.devel.redhat.com/taskinfo?taskID=2272784 Upstream status: http://lkml.org/lkml/2008/3/28/225 Test status: I tested it with the test case in BZ, it runs fine. Signed-off-by: WANG Cong <amwang@redhat.com> diff --git a/Documentation/keys.txt b/Documentation/keys.txt index e373f02..79c4644 100644 --- a/Documentation/keys.txt +++ b/Documentation/keys.txt @@ -170,7 +170,8 @@ The key service provides a number of features besides keys: amount of description and payload space that can be consumed. The user can view information on this and other statistics through procfs - files. + files. The root user may also alter the quota limits through sysctl files + (see the section "New procfs files"). Process-specific and thread-specific keyrings are not counted towards a user's quota. @@ -329,6 +330,27 @@ about the status of the key service: <bytes>/<max> Key size quota +Four new sysctl files have been added also for the purpose of controlling the +quota limits on keys: + + (*) /proc/sys/kernel/keys/root_maxkeys + /proc/sys/kernel/keys/root_maxbytes + + These files hold the maximum number of keys that root may have and the + maximum total number of bytes of data that root may have stored in those + keys. + + (*) /proc/sys/kernel/keys/maxkeys + /proc/sys/kernel/keys/maxbytes + + These files hold the maximum number of keys that each non-root user may + have and the maximum total number of bytes of data that each of those + users may have stored in their keys. + +Root may alter these by writing each new limit as a decimal number string to +the appropriate file. + + =============================== USERSPACE SYSTEM CALL INTERFACE =============================== diff --git a/include/linux/key.h b/include/linux/key.h index 7ed2290..451063a 100644 --- a/include/linux/key.h +++ b/include/linux/key.h @@ -19,6 +19,9 @@ #include <linux/list.h> #include <linux/rbtree.h> #include <linux/rcupdate.h> +#ifndef __GENKSYMS__ +#include <linux/sysctl.h> +#endif #include <asm/atomic.h> #ifdef __KERNEL__ @@ -334,6 +337,12 @@ extern void keyring_replace_payload(struct key *key, void *replacement); #define key_serial(key) ((key) ? (key)->serial : 0) +#ifdef CONFIG_SYSCTL +#ifndef __GENKSYMS__ +extern ctl_table key_sysctls[]; +#endif +#endif + /* * the userspace interface */ diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 0b08bb8..272cc7d 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -45,6 +45,9 @@ #include <linux/syscalls.h> #include <linux/nfs_fs.h> #include <linux/acpi.h> +#ifndef __GENKSYMS__ +#include <linux/key.h> +#endif #include <asm/uaccess.h> #include <asm/processor.h> @@ -894,6 +897,14 @@ static ctl_table kern_table[] = { .mode = 0644, .proc_handler = &proc_dointvec, }, +#ifdef CONFIG_KEYS + { + .ctl_name = CTL_UNNUMBERED, + .procname = "keys", + .mode = 0555, + .child = key_sysctls, + }, +#endif { .ctl_name = 0 } }; diff --git a/security/keys/Makefile b/security/keys/Makefile index 5145adf..747a464 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -14,3 +14,4 @@ obj-y := \ obj-$(CONFIG_KEYS_COMPAT) += compat.o obj-$(CONFIG_PROC_FS) += proc.o +obj-$(CONFIG_SYSCTL) += sysctl.o diff --git a/security/keys/internal.h b/security/keys/internal.h index 1bb416f..1e8cd78 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -46,10 +46,6 @@ struct key_user { int qnbytes; /* number of bytes allocated to this user */ }; -#define KEYQUOTA_MAX_KEYS 100 -#define KEYQUOTA_MAX_BYTES 10000 -#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ - extern struct rb_root key_user_tree; extern spinlock_t key_user_lock; extern struct key_user root_key_user; @@ -57,6 +53,16 @@ extern struct key_user root_key_user; extern struct key_user *key_user_lookup(uid_t uid); extern void key_user_put(struct key_user *user); +/* + * key quota limits + * - root has its own separate limits to everyone else + */ +extern unsigned key_quota_root_maxkeys; +extern unsigned key_quota_root_maxbytes; +extern unsigned key_quota_maxkeys; +extern unsigned key_quota_maxbytes; + +#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ extern struct rb_root key_serial_tree; diff --git a/security/keys/key.c b/security/keys/key.c index d2e3535..98f1f85 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -27,6 +27,11 @@ DEFINE_SPINLOCK(key_serial_lock); struct rb_root key_user_tree; /* tree of quota records indexed by UID */ DEFINE_SPINLOCK(key_user_lock); +unsigned int key_quota_root_maxkeys = 200; /* root's key count quota */ +unsigned int key_quota_root_maxbytes = 20000; /* root's key space quota */ +unsigned int key_quota_maxkeys = 200; /* general key count quota */ +unsigned int key_quota_maxbytes = 20000; /* general key space quota */ + static LIST_HEAD(key_types_list); static DECLARE_RWSEM(key_types_sem); @@ -266,11 +271,16 @@ struct key *key_alloc(struct key_type *type, const char *desc, /* check that the user's quota permits allocation of another key and * its description */ if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + spin_lock(&user->lock); if (!(flags & KEY_ALLOC_QUOTA_OVERRUN)) { - if (user->qnkeys + 1 >= KEYQUOTA_MAX_KEYS || - user->qnbytes + quotalen >= KEYQUOTA_MAX_BYTES - ) + if (user->qnkeys + 1 >= maxkeys || + user->qnbytes + quotalen >= maxbytes || + user->qnbytes + quotalen < user->qnbytes) goto no_quota; } @@ -377,11 +387,14 @@ int key_payload_reserve(struct key *key, size_t datalen) /* contemplate the quota adjustment */ if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxbytes = (key->user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + spin_lock(&key->user->lock); if (delta > 0 && - key->user->qnbytes + delta > KEYQUOTA_MAX_BYTES - ) { + (key->user->qnbytes + delta >= maxbytes || + key->user->qnbytes + delta < key->user->qnbytes)) { ret = -EDQUOT; } else { diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 52627ce..5f759b2 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -727,10 +727,16 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) /* transfer the quota burden to the new user */ if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + spin_lock(&newowner->lock); - if (newowner->qnkeys + 1 >= KEYQUOTA_MAX_KEYS || - newowner->qnbytes + key->quotalen >= - KEYQUOTA_MAX_BYTES) + if (newowner->qnkeys + 1 >= maxkeys || + newowner->qnbytes + key->quotalen >= maxbytes || + newowner->qnbytes + key->quotalen < + newowner->qnbytes) goto quota_overrun; newowner->qnkeys++; diff --git a/security/keys/proc.c b/security/keys/proc.c index 686a9ee..8ed0c2a 100644 --- a/security/keys/proc.c +++ b/security/keys/proc.c @@ -246,6 +246,10 @@ static int proc_key_users_show(struct seq_file *m, void *v) { struct rb_node *_p = v; struct key_user *user = rb_entry(_p, struct key_user, node); + unsigned maxkeys = (user->uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n", user->uid, @@ -253,10 +257,9 @@ static int proc_key_users_show(struct seq_file *m, void *v) atomic_read(&user->nkeys), atomic_read(&user->nikeys), user->qnkeys, - KEYQUOTA_MAX_KEYS, + maxkeys, user->qnbytes, - KEYQUOTA_MAX_BYTES - ); + maxbytes); return 0; diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c new file mode 100644 index 0000000..ea0cfd7 --- /dev/null +++ b/security/keys/sysctl.c @@ -0,0 +1,52 @@ +/* Key management controls + * + * Copyright (C) 2008 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 Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/key.h> +#include <linux/sysctl.h> +#include "internal.h" + +#define CTL_UNNUMBERED -2 + +ctl_table key_sysctls[] = { + { + .ctl_name = CTL_UNNUMBERED, + .procname = "maxkeys", + .data = &key_quota_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "maxbytes", + .data = &key_quota_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "root_maxkeys", + .data = &key_quota_root_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "root_maxbytes", + .data = &key_quota_root_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { .ctl_name = 0 } +};