mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
userns: Make the count of user namespaces per user
Add a structure that is per user and per user ns and use it to hold the count of user namespaces. This makes prevents one user from creating denying service to another user by creating the maximum number of user namespaces. Rename the sysctl export of the maximum count from /proc/sys/userns/max_user_namespaces to /proc/sys/user/max_user_namespaces to reflect that the count is now per user. Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
This commit is contained in:
parent
b376c3e1b6
commit
f6b2db1a3e
@ -22,6 +22,7 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */
|
||||
|
||||
#define USERNS_INIT_FLAGS USERNS_SETGROUPS_ALLOWED
|
||||
|
||||
struct ucounts;
|
||||
struct user_namespace {
|
||||
struct uid_gid_map uid_map;
|
||||
struct uid_gid_map gid_map;
|
||||
@ -44,15 +45,24 @@ struct user_namespace {
|
||||
struct ctl_table_set set;
|
||||
struct ctl_table_header *sysctls;
|
||||
#endif
|
||||
struct ucounts *ucounts;
|
||||
int max_user_namespaces;
|
||||
};
|
||||
|
||||
struct ucounts {
|
||||
struct hlist_node node;
|
||||
struct user_namespace *ns;
|
||||
kuid_t uid;
|
||||
atomic_t count;
|
||||
atomic_t user_namespaces;
|
||||
};
|
||||
|
||||
extern struct user_namespace init_user_ns;
|
||||
extern bool setup_userns_sysctls(struct user_namespace *ns);
|
||||
extern void retire_userns_sysctls(struct user_namespace *ns);
|
||||
extern bool inc_user_namespaces(struct user_namespace *ns);
|
||||
extern void dec_user_namespaces(struct user_namespace *ns);
|
||||
|
||||
bool setup_userns_sysctls(struct user_namespace *ns);
|
||||
void retire_userns_sysctls(struct user_namespace *ns);
|
||||
struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid);
|
||||
void dec_user_namespaces(struct ucounts *ucounts);
|
||||
|
||||
#ifdef CONFIG_USER_NS
|
||||
|
||||
|
@ -322,7 +322,7 @@ void __init fork_init(void)
|
||||
init_task.signal->rlim[RLIMIT_SIGPENDING] =
|
||||
init_task.signal->rlim[RLIMIT_NPROC];
|
||||
|
||||
init_user_ns.max_user_namespaces = max_threads;
|
||||
init_user_ns.max_user_namespaces = max_threads/2;
|
||||
}
|
||||
|
||||
int __weak arch_dup_task_struct(struct task_struct *dst,
|
||||
|
116
kernel/ucount.c
116
kernel/ucount.c
@ -8,8 +8,20 @@
|
||||
#include <linux/stat.h>
|
||||
#include <linux/sysctl.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/user_namespace.h>
|
||||
|
||||
#define UCOUNTS_HASHTABLE_BITS 10
|
||||
static struct hlist_head ucounts_hashtable[(1 << UCOUNTS_HASHTABLE_BITS)];
|
||||
static DEFINE_SPINLOCK(ucounts_lock);
|
||||
|
||||
#define ucounts_hashfn(ns, uid) \
|
||||
hash_long((unsigned long)__kuid_val(uid) + (unsigned long)(ns), \
|
||||
UCOUNTS_HASHTABLE_BITS)
|
||||
#define ucounts_hashentry(ns, uid) \
|
||||
(ucounts_hashtable + ucounts_hashfn(ns, uid))
|
||||
|
||||
|
||||
#ifdef CONFIG_SYSCTL
|
||||
static struct ctl_table_set *
|
||||
set_lookup(struct ctl_table_root *root)
|
||||
@ -45,7 +57,7 @@ static struct ctl_table_root set_root = {
|
||||
|
||||
static int zero = 0;
|
||||
static int int_max = INT_MAX;
|
||||
static struct ctl_table userns_table[] = {
|
||||
static struct ctl_table user_table[] = {
|
||||
{
|
||||
.procname = "max_user_namespaces",
|
||||
.data = &init_user_ns.max_user_namespaces,
|
||||
@ -64,11 +76,11 @@ bool setup_userns_sysctls(struct user_namespace *ns)
|
||||
#ifdef CONFIG_SYSCTL
|
||||
struct ctl_table *tbl;
|
||||
setup_sysctl_set(&ns->set, &set_root, set_is_seen);
|
||||
tbl = kmemdup(userns_table, sizeof(userns_table), GFP_KERNEL);
|
||||
tbl = kmemdup(user_table, sizeof(user_table), GFP_KERNEL);
|
||||
if (tbl) {
|
||||
tbl[0].data = &ns->max_user_namespaces;
|
||||
|
||||
ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl);
|
||||
ns->sysctls = __register_sysctl_table(&ns->set, "user", tbl);
|
||||
}
|
||||
if (!ns->sysctls) {
|
||||
kfree(tbl);
|
||||
@ -91,6 +103,61 @@ void retire_userns_sysctls(struct user_namespace *ns)
|
||||
#endif
|
||||
}
|
||||
|
||||
static struct ucounts *find_ucounts(struct user_namespace *ns, kuid_t uid, struct hlist_head *hashent)
|
||||
{
|
||||
struct ucounts *ucounts;
|
||||
|
||||
hlist_for_each_entry(ucounts, hashent, node) {
|
||||
if (uid_eq(ucounts->uid, uid) && (ucounts->ns == ns))
|
||||
return ucounts;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid)
|
||||
{
|
||||
struct hlist_head *hashent = ucounts_hashentry(ns, uid);
|
||||
struct ucounts *ucounts, *new;
|
||||
|
||||
spin_lock(&ucounts_lock);
|
||||
ucounts = find_ucounts(ns, uid, hashent);
|
||||
if (!ucounts) {
|
||||
spin_unlock(&ucounts_lock);
|
||||
|
||||
new = kzalloc(sizeof(*new), GFP_KERNEL);
|
||||
if (!new)
|
||||
return NULL;
|
||||
|
||||
new->ns = ns;
|
||||
new->uid = uid;
|
||||
atomic_set(&new->count, 0);
|
||||
|
||||
spin_lock(&ucounts_lock);
|
||||
ucounts = find_ucounts(ns, uid, hashent);
|
||||
if (ucounts) {
|
||||
kfree(new);
|
||||
} else {
|
||||
hlist_add_head(&new->node, hashent);
|
||||
ucounts = new;
|
||||
}
|
||||
}
|
||||
if (!atomic_add_unless(&ucounts->count, 1, INT_MAX))
|
||||
ucounts = NULL;
|
||||
spin_unlock(&ucounts_lock);
|
||||
return ucounts;
|
||||
}
|
||||
|
||||
static void put_ucounts(struct ucounts *ucounts)
|
||||
{
|
||||
if (atomic_dec_and_test(&ucounts->count)) {
|
||||
spin_lock(&ucounts_lock);
|
||||
hlist_del_init(&ucounts->node);
|
||||
spin_unlock(&ucounts_lock);
|
||||
|
||||
kfree(ucounts);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool atomic_inc_below(atomic_t *v, int u)
|
||||
{
|
||||
int c, old;
|
||||
@ -105,44 +172,51 @@ static inline bool atomic_inc_below(atomic_t *v, int u)
|
||||
}
|
||||
}
|
||||
|
||||
bool inc_user_namespaces(struct user_namespace *ns)
|
||||
struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid)
|
||||
{
|
||||
struct user_namespace *pos, *bad;
|
||||
for (pos = ns; pos; pos = pos->parent) {
|
||||
int max = READ_ONCE(pos->max_user_namespaces);
|
||||
if (!atomic_inc_below(&pos->user_namespaces, max))
|
||||
struct ucounts *ucounts, *iter, *bad;
|
||||
struct user_namespace *tns;
|
||||
ucounts = get_ucounts(ns, uid);
|
||||
for (iter = ucounts; iter; iter = tns->ucounts) {
|
||||
int max;
|
||||
tns = iter->ns;
|
||||
max = READ_ONCE(tns->max_user_namespaces);
|
||||
if (!atomic_inc_below(&iter->user_namespaces, max))
|
||||
goto fail;
|
||||
}
|
||||
return true;
|
||||
return ucounts;
|
||||
fail:
|
||||
bad = pos;
|
||||
for (pos = ns; pos != bad; pos = pos->parent)
|
||||
atomic_dec(&pos->user_namespaces);
|
||||
bad = iter;
|
||||
for (iter = ucounts; iter != bad; iter = iter->ns->ucounts)
|
||||
atomic_dec(&iter->user_namespaces);
|
||||
|
||||
return false;
|
||||
put_ucounts(ucounts);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void dec_user_namespaces(struct user_namespace *ns)
|
||||
void dec_user_namespaces(struct ucounts *ucounts)
|
||||
{
|
||||
struct user_namespace *pos;
|
||||
for (pos = ns; pos; pos = pos->parent) {
|
||||
int dec = atomic_dec_if_positive(&pos->user_namespaces);
|
||||
struct ucounts *iter;
|
||||
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
|
||||
int dec = atomic_dec_if_positive(&iter->user_namespaces);
|
||||
WARN_ON_ONCE(dec < 0);
|
||||
}
|
||||
put_ucounts(ucounts);
|
||||
}
|
||||
|
||||
|
||||
static __init int user_namespace_sysctl_init(void)
|
||||
{
|
||||
#ifdef CONFIG_SYSCTL
|
||||
static struct ctl_table_header *userns_header;
|
||||
static struct ctl_table_header *user_header;
|
||||
static struct ctl_table empty[1];
|
||||
/*
|
||||
* It is necessary to register the userns directory in the
|
||||
* It is necessary to register the user directory in the
|
||||
* default set so that registrations in the child sets work
|
||||
* properly.
|
||||
*/
|
||||
userns_header = register_sysctl("userns", empty);
|
||||
BUG_ON(!userns_header);
|
||||
user_header = register_sysctl("user", empty);
|
||||
BUG_ON(!user_header);
|
||||
BUG_ON(!setup_userns_sysctls(&init_user_ns));
|
||||
#endif
|
||||
return 0;
|
||||
|
@ -31,7 +31,6 @@ static bool new_idmap_permitted(const struct file *file,
|
||||
struct uid_gid_map *map);
|
||||
static void free_user_ns(struct work_struct *work);
|
||||
|
||||
|
||||
static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns)
|
||||
{
|
||||
/* Start with the same capabilities as init but useless for doing
|
||||
@ -64,13 +63,15 @@ int create_user_ns(struct cred *new)
|
||||
struct user_namespace *ns, *parent_ns = new->user_ns;
|
||||
kuid_t owner = new->euid;
|
||||
kgid_t group = new->egid;
|
||||
struct ucounts *ucounts;
|
||||
int ret;
|
||||
|
||||
ret = -EUSERS;
|
||||
if (parent_ns->level > 32)
|
||||
goto fail;
|
||||
|
||||
if (!inc_user_namespaces(parent_ns))
|
||||
ucounts = inc_user_namespaces(parent_ns, owner);
|
||||
if (!ucounts)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
@ -110,6 +111,7 @@ int create_user_ns(struct cred *new)
|
||||
ns->group = group;
|
||||
INIT_WORK(&ns->work, free_user_ns);
|
||||
ns->max_user_namespaces = INT_MAX;
|
||||
ns->ucounts = ucounts;
|
||||
|
||||
/* Inherit USERNS_SETGROUPS_ALLOWED from our parent */
|
||||
mutex_lock(&userns_state_mutex);
|
||||
@ -133,7 +135,7 @@ fail_keyring:
|
||||
fail_free:
|
||||
kmem_cache_free(user_ns_cachep, ns);
|
||||
fail_dec:
|
||||
dec_user_namespaces(parent_ns);
|
||||
dec_user_namespaces(ucounts);
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
@ -164,6 +166,7 @@ static void free_user_ns(struct work_struct *work)
|
||||
container_of(work, struct user_namespace, work);
|
||||
|
||||
do {
|
||||
struct ucounts *ucounts = ns->ucounts;
|
||||
parent = ns->parent;
|
||||
retire_userns_sysctls(ns);
|
||||
#ifdef CONFIG_PERSISTENT_KEYRINGS
|
||||
@ -171,7 +174,7 @@ static void free_user_ns(struct work_struct *work)
|
||||
#endif
|
||||
ns_free_inum(&ns->ns);
|
||||
kmem_cache_free(user_ns_cachep, ns);
|
||||
dec_user_namespaces(parent);
|
||||
dec_user_namespaces(ucounts);
|
||||
ns = parent;
|
||||
} while (atomic_dec_and_test(&parent->count));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user