From 13bcc6a2853435bb5dad368bcbaa9d2a5b9c0ac4 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Sat, 16 Jul 2016 15:22:55 -0500 Subject: sysctl: Stop implicitly passing current into sysctl_table_root.lookup Passing nsproxy into sysctl_table_root.lookup was a premature optimization in attempt to avoid depending on current. The directory /proc/self/sys has not appeared and if and when it does this code will need to be reviewed closely and reworked anyway. So remove the premature optimization. Acked-by: Kees Cook Acked-by: Serge Hallyn Signed-off-by: "Eric W. Biederman" --- fs/proc/proc_sysctl.c | 14 +++++++------- include/linux/sysctl.h | 3 +-- net/sysctl_net.c | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 1b93650dda2f..a80acdfbe180 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -72,7 +72,7 @@ static DEFINE_SPINLOCK(sysctl_lock); static void drop_sysctl_table(struct ctl_table_header *header); static int sysctl_follow_link(struct ctl_table_header **phead, - struct ctl_table **pentry, struct nsproxy *namespaces); + struct ctl_table **pentry); static int insert_links(struct ctl_table_header *head); static void put_links(struct ctl_table_header *header); @@ -319,11 +319,11 @@ static void sysctl_head_finish(struct ctl_table_header *head) } static struct ctl_table_set * -lookup_header_set(struct ctl_table_root *root, struct nsproxy *namespaces) +lookup_header_set(struct ctl_table_root *root) { struct ctl_table_set *set = &root->default_set; if (root->lookup) - set = root->lookup(root, namespaces); + set = root->lookup(root); return set; } @@ -491,7 +491,7 @@ static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry, goto out; if (S_ISLNK(p->mode)) { - ret = sysctl_follow_link(&h, &p, current->nsproxy); + ret = sysctl_follow_link(&h, &p); err = ERR_PTR(ret); if (ret) goto out; @@ -659,7 +659,7 @@ static bool proc_sys_link_fill_cache(struct file *file, if (S_ISLNK(table->mode)) { /* It is not an error if we can not follow the link ignore it */ - int err = sysctl_follow_link(&head, &table, current->nsproxy); + int err = sysctl_follow_link(&head, &table); if (err) goto out; } @@ -976,7 +976,7 @@ static struct ctl_dir *xlate_dir(struct ctl_table_set *set, struct ctl_dir *dir) } static int sysctl_follow_link(struct ctl_table_header **phead, - struct ctl_table **pentry, struct nsproxy *namespaces) + struct ctl_table **pentry) { struct ctl_table_header *head; struct ctl_table_root *root; @@ -988,7 +988,7 @@ static int sysctl_follow_link(struct ctl_table_header **phead, ret = 0; spin_lock(&sysctl_lock); root = (*pentry)->data; - set = lookup_header_set(root, namespaces); + set = lookup_header_set(root); dir = xlate_dir(set, (*phead)->parent); if (IS_ERR(dir)) ret = PTR_ERR(dir); diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 697e160c78d0..f166ca0203e2 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -155,8 +155,7 @@ struct ctl_table_set { struct ctl_table_root { struct ctl_table_set default_set; - struct ctl_table_set *(*lookup)(struct ctl_table_root *root, - struct nsproxy *namespaces); + struct ctl_table_set *(*lookup)(struct ctl_table_root *root); int (*permissions)(struct ctl_table_header *head, struct ctl_table *table); }; diff --git a/net/sysctl_net.c b/net/sysctl_net.c index 46a71c701e7c..ba9b5d1a31df 100644 --- a/net/sysctl_net.c +++ b/net/sysctl_net.c @@ -27,9 +27,9 @@ #endif static struct ctl_table_set * -net_ctl_header_lookup(struct ctl_table_root *root, struct nsproxy *namespaces) +net_ctl_header_lookup(struct ctl_table_root *root) { - return &namespaces->net_ns->sysctls; + return ¤t->nsproxy->net_ns->sysctls; } static int is_seen(struct ctl_table_set *set) -- cgit v1.2.3-59-g8ed1b From b032132c3c218f4a09e9499b3674299a752581c6 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Sat, 30 Jul 2016 13:53:37 -0500 Subject: userns: Free user namespaces in process context Add the necessary boiler plate to move freeing of user namespaces into work queue and thus into process context where things can sleep. This is a necessary precursor to per user namespace sysctls. Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 5 +++-- kernel/user_namespace.c | 14 +++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 9217169c64cb..4e79b3c64dee 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -39,6 +39,7 @@ struct user_namespace { struct key *persistent_keyring_register; struct rw_semaphore persistent_keyring_register_sem; #endif + struct work_struct work; }; extern struct user_namespace init_user_ns; @@ -54,12 +55,12 @@ static inline struct user_namespace *get_user_ns(struct user_namespace *ns) extern int create_user_ns(struct cred *new); extern int unshare_userns(unsigned long unshare_flags, struct cred **new_cred); -extern void free_user_ns(struct user_namespace *ns); +extern void __put_user_ns(struct user_namespace *ns); static inline void put_user_ns(struct user_namespace *ns) { if (ns && atomic_dec_and_test(&ns->count)) - free_user_ns(ns); + __put_user_ns(ns); } struct seq_operations; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 68f594212759..5247cdb24e62 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -29,6 +29,7 @@ static DEFINE_MUTEX(userns_state_mutex); static bool new_idmap_permitted(const struct file *file, struct user_namespace *ns, int cap_setid, 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) { @@ -101,6 +102,7 @@ int create_user_ns(struct cred *new) ns->level = parent_ns->level + 1; ns->owner = owner; ns->group = group; + INIT_WORK(&ns->work, free_user_ns); /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ mutex_lock(&userns_state_mutex); @@ -135,9 +137,10 @@ int unshare_userns(unsigned long unshare_flags, struct cred **new_cred) return err; } -void free_user_ns(struct user_namespace *ns) +static void free_user_ns(struct work_struct *work) { - struct user_namespace *parent; + struct user_namespace *parent, *ns = + container_of(work, struct user_namespace, work); do { parent = ns->parent; @@ -149,7 +152,12 @@ void free_user_ns(struct user_namespace *ns) ns = parent; } while (atomic_dec_and_test(&parent->count)); } -EXPORT_SYMBOL(free_user_ns); + +void __put_user_ns(struct user_namespace *ns) +{ + schedule_work(&ns->work); +} +EXPORT_SYMBOL(__put_user_ns); static u32 map_id_range_down(struct uid_gid_map *map, u32 id, u32 count) { -- cgit v1.2.3-59-g8ed1b From dbec28460a89aa7c02c3301e9e108d98272549d2 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Sat, 30 Jul 2016 13:58:49 -0500 Subject: userns: Add per user namespace sysctls. Limit per userns sysctls to only be opened for write by a holder of CAP_SYS_RESOURCE. Add all of the necessary boilerplate for having per user namespace sysctls. Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 4 ++ kernel/Makefile | 2 +- kernel/ucount.c | 99 ++++++++++++++++++++++++++++++++++++++++++ kernel/user_namespace.c | 18 +++++++- 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 kernel/ucount.c diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 4e79b3c64dee..e5697eaf6bf9 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -40,6 +40,10 @@ struct user_namespace { struct rw_semaphore persistent_keyring_register_sem; #endif struct work_struct work; +#ifdef CONFIG_SYSCTL + struct ctl_table_set set; + struct ctl_table_header *sysctls; +#endif }; extern struct user_namespace init_user_ns; diff --git a/kernel/Makefile b/kernel/Makefile index e2ec54e2b952..eb26e12c6c2a 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -9,7 +9,7 @@ obj-y = fork.o exec_domain.o panic.o \ extable.o params.o \ kthread.o sys_ni.o nsproxy.o \ notifier.o ksysfs.o cred.o reboot.o \ - async.o range.o smpboot.o + async.o range.o smpboot.o ucount.o obj-$(CONFIG_MULTIUSER) += groups.o diff --git a/kernel/ucount.c b/kernel/ucount.c new file mode 100644 index 000000000000..cbde1dc87851 --- /dev/null +++ b/kernel/ucount.c @@ -0,0 +1,99 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include +#include +#include + +#ifdef CONFIG_SYSCTL +static struct ctl_table_set * +set_lookup(struct ctl_table_root *root) +{ + return ¤t_user_ns()->set; +} + +static int set_is_seen(struct ctl_table_set *set) +{ + return ¤t_user_ns()->set == set; +} + +static int set_permissions(struct ctl_table_header *head, + struct ctl_table *table) +{ + struct user_namespace *user_ns = + container_of(head->set, struct user_namespace, set); + int mode; + + /* Allow users with CAP_SYS_RESOURCE unrestrained access */ + if (ns_capable(user_ns, CAP_SYS_RESOURCE)) + mode = (table->mode & S_IRWXU) >> 6; + else + /* Allow all others at most read-only access */ + mode = table->mode & S_IROTH; + return (mode << 6) | (mode << 3) | mode; +} + +static struct ctl_table_root set_root = { + .lookup = set_lookup, + .permissions = set_permissions, +}; + +static struct ctl_table userns_table[] = { + { } +}; +#endif /* CONFIG_SYSCTL */ + +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); + if (tbl) { + ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl); + } + if (!ns->sysctls) { + kfree(tbl); + retire_sysctl_set(&ns->set); + return false; + } +#endif + return true; +} + +void retire_userns_sysctls(struct user_namespace *ns) +{ +#ifdef CONFIG_SYSCTL + struct ctl_table *tbl; + + tbl = ns->sysctls->ctl_table_arg; + unregister_sysctl_table(ns->sysctls); + retire_sysctl_set(&ns->set); + kfree(tbl); +#endif +} + +static __init int user_namespace_sysctl_init(void) +{ +#ifdef CONFIG_SYSCTL + static struct ctl_table_header *userns_header; + static struct ctl_table empty[1]; + /* + * It is necessary to register the userns directory in the + * default set so that registrations in the child sets work + * properly. + */ + userns_header = register_sysctl("userns", empty); + BUG_ON(!userns_header); + BUG_ON(!setup_userns_sysctls(&init_user_ns)); +#endif + return 0; +} +subsys_initcall(user_namespace_sysctl_init); + + diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 5247cdb24e62..a63332253c7e 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -23,6 +23,9 @@ #include #include +extern bool setup_userns_sysctls(struct user_namespace *ns); +extern void retire_userns_sysctls(struct user_namespace *ns); + static struct kmem_cache *user_ns_cachep __read_mostly; static DEFINE_MUTEX(userns_state_mutex); @@ -109,12 +112,22 @@ int create_user_ns(struct cred *new) ns->flags = parent_ns->flags; mutex_unlock(&userns_state_mutex); - set_cred_user_ns(new, ns); - #ifdef CONFIG_PERSISTENT_KEYRINGS init_rwsem(&ns->persistent_keyring_register_sem); #endif + ret = -ENOMEM; + if (!setup_userns_sysctls(ns)) + goto fail_keyring; + + set_cred_user_ns(new, ns); return 0; +fail_keyring: +#ifdef CONFIG_PERSISTENT_KEYRINGS + key_put(ns->persistent_keyring_register); +#endif + ns_free_inum(&ns->ns); + kmem_cache_free(user_ns_cachep, ns); + return ret; } int unshare_userns(unsigned long unshare_flags, struct cred **new_cred) @@ -144,6 +157,7 @@ static void free_user_ns(struct work_struct *work) do { parent = ns->parent; + retire_userns_sysctls(ns); #ifdef CONFIG_PERSISTENT_KEYRINGS key_put(ns->persistent_keyring_register); #endif -- cgit v1.2.3-59-g8ed1b From b376c3e1b6770ddcb4f0782be16358095fcea0b6 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 13:41:24 -0500 Subject: userns: Add a limit on the number of user namespaces Export the export the maximum number of user namespaces as /proc/sys/userns/max_user_namespaces. Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 6 +++++ kernel/fork.c | 2 ++ kernel/ucount.c | 53 ++++++++++++++++++++++++++++++++++++++++++ kernel/user_namespace.c | 31 +++++++++++++++--------- 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index e5697eaf6bf9..6421cca2daa9 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -44,9 +44,15 @@ struct user_namespace { struct ctl_table_set set; struct ctl_table_header *sysctls; #endif + int max_user_namespaces; + 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); #ifdef CONFIG_USER_NS diff --git a/kernel/fork.c b/kernel/fork.c index 52e725d4a866..daa6a82b4900 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -321,6 +321,8 @@ void __init fork_init(void) init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2; init_task.signal->rlim[RLIMIT_SIGPENDING] = init_task.signal->rlim[RLIMIT_NPROC]; + + init_user_ns.max_user_namespaces = max_threads; } int __weak arch_dup_task_struct(struct task_struct *dst, diff --git a/kernel/ucount.c b/kernel/ucount.c index cbde1dc87851..6c2205c0befd 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -43,7 +43,18 @@ static struct ctl_table_root set_root = { .permissions = set_permissions, }; +static int zero = 0; +static int int_max = INT_MAX; static struct ctl_table userns_table[] = { + { + .procname = "max_user_namespaces", + .data = &init_user_ns.max_user_namespaces, + .maxlen = sizeof(init_user_ns.max_user_namespaces), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &zero, + .extra2 = &int_max, + }, { } }; #endif /* CONFIG_SYSCTL */ @@ -55,6 +66,8 @@ bool setup_userns_sysctls(struct user_namespace *ns) setup_sysctl_set(&ns->set, &set_root, set_is_seen); tbl = kmemdup(userns_table, sizeof(userns_table), GFP_KERNEL); if (tbl) { + tbl[0].data = &ns->max_user_namespaces; + ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl); } if (!ns->sysctls) { @@ -78,6 +91,46 @@ void retire_userns_sysctls(struct user_namespace *ns) #endif } +static inline bool atomic_inc_below(atomic_t *v, int u) +{ + int c, old; + c = atomic_read(v); + for (;;) { + if (unlikely(c >= u)) + return false; + old = atomic_cmpxchg(v, c, c+1); + if (likely(old == c)) + return true; + c = old; + } +} + +bool inc_user_namespaces(struct user_namespace *ns) +{ + 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)) + goto fail; + } + return true; +fail: + bad = pos; + for (pos = ns; pos != bad; pos = pos->parent) + atomic_dec(&pos->user_namespaces); + + return false; +} + +void dec_user_namespaces(struct user_namespace *ns) +{ + struct user_namespace *pos; + for (pos = ns; pos; pos = pos->parent) { + int dec = atomic_dec_if_positive(&pos->user_namespaces); + WARN_ON_ONCE(dec < 0); + } +} + static __init int user_namespace_sysctl_init(void) { #ifdef CONFIG_SYSCTL diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index a63332253c7e..7d87017a0040 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -23,9 +23,6 @@ #include #include -extern bool setup_userns_sysctls(struct user_namespace *ns); -extern void retire_userns_sysctls(struct user_namespace *ns); - static struct kmem_cache *user_ns_cachep __read_mostly; static DEFINE_MUTEX(userns_state_mutex); @@ -34,6 +31,7 @@ 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 @@ -68,8 +66,12 @@ int create_user_ns(struct cred *new) kgid_t group = new->egid; int ret; + ret = -EUSERS; if (parent_ns->level > 32) - return -EUSERS; + goto fail; + + if (!inc_user_namespaces(parent_ns)) + goto fail; /* * Verify that we can not violate the policy of which files @@ -77,26 +79,27 @@ int create_user_ns(struct cred *new) * by verifing that the root directory is at the root of the * mount namespace which allows all files to be accessed. */ + ret = -EPERM; if (current_chrooted()) - return -EPERM; + goto fail_dec; /* The creator needs a mapping in the parent user namespace * or else we won't be able to reasonably tell userspace who * created a user_namespace. */ + ret = -EPERM; if (!kuid_has_mapping(parent_ns, owner) || !kgid_has_mapping(parent_ns, group)) - return -EPERM; + goto fail_dec; + ret = -ENOMEM; ns = kmem_cache_zalloc(user_ns_cachep, GFP_KERNEL); if (!ns) - return -ENOMEM; + goto fail_dec; ret = ns_alloc_inum(&ns->ns); - if (ret) { - kmem_cache_free(user_ns_cachep, ns); - return ret; - } + if (ret) + goto fail_free; ns->ns.ops = &userns_operations; atomic_set(&ns->count, 1); @@ -106,6 +109,7 @@ int create_user_ns(struct cred *new) ns->owner = owner; ns->group = group; INIT_WORK(&ns->work, free_user_ns); + ns->max_user_namespaces = INT_MAX; /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ mutex_lock(&userns_state_mutex); @@ -126,7 +130,11 @@ fail_keyring: key_put(ns->persistent_keyring_register); #endif ns_free_inum(&ns->ns); +fail_free: kmem_cache_free(user_ns_cachep, ns); +fail_dec: + dec_user_namespaces(parent_ns); +fail: return ret; } @@ -163,6 +171,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); ns = parent; } while (atomic_dec_and_test(&parent->count)); } -- cgit v1.2.3-59-g8ed1b From f6b2db1a3e8d141dd144df58900fb0444d5d7c53 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 13:54:50 -0500 Subject: 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" --- include/linux/user_namespace.h | 18 +++++-- kernel/fork.c | 2 +- kernel/ucount.c | 116 +++++++++++++++++++++++++++++++++-------- kernel/user_namespace.c | 11 ++-- 4 files changed, 117 insertions(+), 30 deletions(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 6421cca2daa9..826de7a12a20 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -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 diff --git a/kernel/fork.c b/kernel/fork.c index daa6a82b4900..d8cde533ace3 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -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, diff --git a/kernel/ucount.c b/kernel/ucount.c index 6c2205c0befd..33c418718304 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -8,8 +8,20 @@ #include #include #include +#include #include +#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; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 7d87017a0040..58c67e5f851c 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -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)); } -- cgit v1.2.3-59-g8ed1b From 25f9c0817c535a728c1088542230fa327c577c9e Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:41:52 -0500 Subject: userns: Generalize the user namespace count into ucount The same kind of recursive sane default limit and policy countrol that has been implemented for the user namespace is desirable for the other namespaces, so generalize the user namespace refernce count into a ucount. Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 14 ++++++++++---- kernel/fork.c | 5 ++++- kernel/ucount.c | 39 +++++++++++++++++++++------------------ kernel/user_namespace.c | 16 ++++++++++++++-- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 826de7a12a20..9b676ead35c3 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -23,6 +23,12 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */ #define USERNS_INIT_FLAGS USERNS_SETGROUPS_ALLOWED struct ucounts; + +enum ucount_type { + UCOUNT_USER_NAMESPACES, + UCOUNT_COUNTS, +}; + struct user_namespace { struct uid_gid_map uid_map; struct uid_gid_map gid_map; @@ -46,7 +52,7 @@ struct user_namespace { struct ctl_table_header *sysctls; #endif struct ucounts *ucounts; - int max_user_namespaces; + int ucount_max[UCOUNT_COUNTS]; }; struct ucounts { @@ -54,15 +60,15 @@ struct ucounts { struct user_namespace *ns; kuid_t uid; atomic_t count; - atomic_t user_namespaces; + atomic_t ucount[UCOUNT_COUNTS]; }; extern struct user_namespace init_user_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); +struct ucounts *inc_ucount(struct user_namespace *ns, kuid_t uid, enum ucount_type type); +void dec_ucount(struct ucounts *ucounts, enum ucount_type type); #ifdef CONFIG_USER_NS diff --git a/kernel/fork.c b/kernel/fork.c index d8cde533ace3..3cb4853a59aa 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -302,6 +302,7 @@ int arch_task_struct_size __read_mostly; void __init fork_init(void) { + int i; #ifndef CONFIG_ARCH_TASK_STRUCT_ALLOCATOR #ifndef ARCH_MIN_TASKALIGN #define ARCH_MIN_TASKALIGN L1_CACHE_BYTES @@ -322,7 +323,9 @@ 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/2; + for (i = 0; i < UCOUNT_COUNTS; i++) { + init_user_ns.ucount_max[i] = max_threads/2; + } } int __weak arch_dup_task_struct(struct task_struct *dst, diff --git a/kernel/ucount.c b/kernel/ucount.c index 33c418718304..0f9ab3b26185 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -57,16 +57,17 @@ static struct ctl_table_root set_root = { static int zero = 0; static int int_max = INT_MAX; +#define UCOUNT_ENTRY(name) \ + { \ + .procname = name, \ + .maxlen = sizeof(int), \ + .mode = 0644, \ + .proc_handler = proc_dointvec_minmax, \ + .extra1 = &zero, \ + .extra2 = &int_max, \ + } static struct ctl_table user_table[] = { - { - .procname = "max_user_namespaces", - .data = &init_user_ns.max_user_namespaces, - .maxlen = sizeof(init_user_ns.max_user_namespaces), - .mode = 0644, - .proc_handler = proc_dointvec_minmax, - .extra1 = &zero, - .extra2 = &int_max, - }, + UCOUNT_ENTRY("max_user_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ @@ -78,8 +79,10 @@ bool setup_userns_sysctls(struct user_namespace *ns) setup_sysctl_set(&ns->set, &set_root, set_is_seen); tbl = kmemdup(user_table, sizeof(user_table), GFP_KERNEL); if (tbl) { - tbl[0].data = &ns->max_user_namespaces; - + int i; + for (i = 0; i < UCOUNT_COUNTS; i++) { + tbl[i].data = &ns->ucount_max[i]; + } ns->sysctls = __register_sysctl_table(&ns->set, "user", tbl); } if (!ns->sysctls) { @@ -172,7 +175,8 @@ static inline bool atomic_inc_below(atomic_t *v, int u) } } -struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid) +struct ucounts *inc_ucount(struct user_namespace *ns, kuid_t uid, + enum ucount_type type) { struct ucounts *ucounts, *iter, *bad; struct user_namespace *tns; @@ -180,31 +184,30 @@ struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t 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)) + max = READ_ONCE(tns->ucount_max[type]); + if (!atomic_inc_below(&iter->ucount[type], max)) goto fail; } return ucounts; fail: bad = iter; for (iter = ucounts; iter != bad; iter = iter->ns->ucounts) - atomic_dec(&iter->user_namespaces); + atomic_dec(&iter->ucount[type]); put_ucounts(ucounts); return NULL; } -void dec_user_namespaces(struct ucounts *ucounts) +void dec_ucount(struct ucounts *ucounts, enum ucount_type type) { struct ucounts *iter; for (iter = ucounts; iter; iter = iter->ns->ucounts) { - int dec = atomic_dec_if_positive(&iter->user_namespaces); + int dec = atomic_dec_if_positive(&iter->ucount[type]); WARN_ON_ONCE(dec < 0); } put_ucounts(ucounts); } - static __init int user_namespace_sysctl_init(void) { #ifdef CONFIG_SYSCTL diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 58c67e5f851c..0edafe305861 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -31,6 +31,16 @@ static bool new_idmap_permitted(const struct file *file, struct uid_gid_map *map); static void free_user_ns(struct work_struct *work); +static struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid) +{ + return inc_ucount(ns, uid, UCOUNT_USER_NAMESPACES); +} + +static void dec_user_namespaces(struct ucounts *ucounts) +{ + return dec_ucount(ucounts, UCOUNT_USER_NAMESPACES); +} + 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,7 +74,7 @@ int create_user_ns(struct cred *new) kuid_t owner = new->euid; kgid_t group = new->egid; struct ucounts *ucounts; - int ret; + int ret, i; ret = -EUSERS; if (parent_ns->level > 32) @@ -110,7 +120,9 @@ int create_user_ns(struct cred *new) ns->owner = owner; ns->group = group; INIT_WORK(&ns->work, free_user_ns); - ns->max_user_namespaces = INT_MAX; + for (i = 0; i < UCOUNT_COUNTS; i++) { + ns->ucount_max[i] = INT_MAX; + } ns->ucounts = ucounts; /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ -- cgit v1.2.3-59-g8ed1b From f333c700c6100b53050980986be922bb21466e29 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:08:36 -0500 Subject: pidns: Add a limit on the number of pid namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/pid_namespace.h | 1 + include/linux/user_namespace.h | 1 + kernel/pid_namespace.c | 25 +++++++++++++++++++++---- kernel/ucount.c | 1 + 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/include/linux/pid_namespace.h b/include/linux/pid_namespace.h index 918b117a7cd3..34cce96741bc 100644 --- a/include/linux/pid_namespace.h +++ b/include/linux/pid_namespace.h @@ -40,6 +40,7 @@ struct pid_namespace { struct fs_pin *bacct; #endif struct user_namespace *user_ns; + struct ucounts *ucounts; struct work_struct proc_work; kgid_t pid_gid; int hide_pid; diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 9b676ead35c3..9ee94827728d 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -26,6 +26,7 @@ struct ucounts; enum ucount_type { UCOUNT_USER_NAMESPACES, + UCOUNT_PID_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/kernel/pid_namespace.c b/kernel/pid_namespace.c index a65ba137fd15..30a7f3351932 100644 --- a/kernel/pid_namespace.c +++ b/kernel/pid_namespace.c @@ -79,23 +79,36 @@ static void proc_cleanup_work(struct work_struct *work) /* MAX_PID_NS_LEVEL is needed for limiting size of 'struct pid' */ #define MAX_PID_NS_LEVEL 32 +static struct ucounts *inc_pid_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_PID_NAMESPACES); +} + +static void dec_pid_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_PID_NAMESPACES); +} + static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns, struct pid_namespace *parent_pid_ns) { struct pid_namespace *ns; unsigned int level = parent_pid_ns->level + 1; + struct ucounts *ucounts; int i; int err; - if (level > MAX_PID_NS_LEVEL) { - err = -EINVAL; + err = -EINVAL; + if (level > MAX_PID_NS_LEVEL) + goto out; + ucounts = inc_pid_namespaces(user_ns); + if (!ucounts) goto out; - } err = -ENOMEM; ns = kmem_cache_zalloc(pid_ns_cachep, GFP_KERNEL); if (ns == NULL) - goto out; + goto out_dec; ns->pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!ns->pidmap[0].page) @@ -114,6 +127,7 @@ static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns ns->level = level; ns->parent = get_pid_ns(parent_pid_ns); ns->user_ns = get_user_ns(user_ns); + ns->ucounts = ucounts; ns->nr_hashed = PIDNS_HASH_ADDING; INIT_WORK(&ns->proc_work, proc_cleanup_work); @@ -129,6 +143,8 @@ out_free_map: kfree(ns->pidmap[0].page); out_free: kmem_cache_free(pid_ns_cachep, ns); +out_dec: + dec_pid_namespaces(ucounts); out: return ERR_PTR(err); } @@ -146,6 +162,7 @@ static void destroy_pid_namespace(struct pid_namespace *ns) ns_free_inum(&ns->ns); for (i = 0; i < PIDMAP_ENTRIES; i++) kfree(ns->pidmap[i].page); + dec_pid_namespaces(ns->ucounts); put_user_ns(ns->user_ns); call_rcu(&ns->rcu, delayed_free_pidns); } diff --git a/kernel/ucount.c b/kernel/ucount.c index 0f9ab3b26185..66eca94e4ada 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -68,6 +68,7 @@ static int int_max = INT_MAX; } static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_user_namespaces"), + UCOUNT_ENTRY("max_pid_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ -- cgit v1.2.3-59-g8ed1b From f7af3d1c03136275b876f58644599b120cf4ffdd Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:11:25 -0500 Subject: utsns: Add a limit on the number of uts namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 1 + include/linux/utsname.h | 1 + kernel/ucount.c | 1 + kernel/utsname.c | 34 +++++++++++++++++++++++++++++----- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 9ee94827728d..f9df7dd2609a 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -27,6 +27,7 @@ struct ucounts; enum ucount_type { UCOUNT_USER_NAMESPACES, UCOUNT_PID_NAMESPACES, + UCOUNT_UTS_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/include/linux/utsname.h b/include/linux/utsname.h index 5093f58ae192..60f0bb83b313 100644 --- a/include/linux/utsname.h +++ b/include/linux/utsname.h @@ -24,6 +24,7 @@ struct uts_namespace { struct kref kref; struct new_utsname name; struct user_namespace *user_ns; + struct ucounts *ucounts; struct ns_common ns; }; extern struct uts_namespace init_uts_ns; diff --git a/kernel/ucount.c b/kernel/ucount.c index 66eca94e4ada..866850e2eb92 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -69,6 +69,7 @@ static int int_max = INT_MAX; static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_user_namespaces"), UCOUNT_ENTRY("max_pid_namespaces"), + UCOUNT_ENTRY("max_uts_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ diff --git a/kernel/utsname.c b/kernel/utsname.c index 831ea7108232..f3b0bb4ac3ba 100644 --- a/kernel/utsname.c +++ b/kernel/utsname.c @@ -17,6 +17,16 @@ #include #include +static struct ucounts *inc_uts_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_UTS_NAMESPACES); +} + +static void dec_uts_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_UTS_NAMESPACES); +} + static struct uts_namespace *create_uts_ns(void) { struct uts_namespace *uts_ns; @@ -36,18 +46,24 @@ static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns, struct uts_namespace *old_ns) { struct uts_namespace *ns; + struct ucounts *ucounts; int err; + err = -ENFILE; + ucounts = inc_uts_namespaces(user_ns); + if (!ucounts) + goto fail; + + err = -ENOMEM; ns = create_uts_ns(); if (!ns) - return ERR_PTR(-ENOMEM); + goto fail_dec; err = ns_alloc_inum(&ns->ns); - if (err) { - kfree(ns); - return ERR_PTR(err); - } + if (err) + goto fail_free; + ns->ucounts = ucounts; ns->ns.ops = &utsns_operations; down_read(&uts_sem); @@ -55,6 +71,13 @@ static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns, ns->user_ns = get_user_ns(user_ns); up_read(&uts_sem); return ns; + +fail_free: + kfree(ns); +fail_dec: + dec_uts_namespaces(ucounts); +fail: + return ERR_PTR(err); } /* @@ -85,6 +108,7 @@ void free_uts_ns(struct kref *kref) struct uts_namespace *ns; ns = container_of(kref, struct uts_namespace, kref); + dec_uts_namespaces(ns->ucounts); put_user_ns(ns->user_ns); ns_free_inum(&ns->ns); kfree(ns); -- cgit v1.2.3-59-g8ed1b From aba356616386e6e573a34c6d64ed12443686e5c8 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:20:23 -0500 Subject: ipcns: Add a limit on the number of ipc namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/ipc_namespace.h | 1 + include/linux/user_namespace.h | 1 + ipc/namespace.c | 45 +++++++++++++++++++++++++++++++----------- kernel/ucount.c | 1 + 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/include/linux/ipc_namespace.h b/include/linux/ipc_namespace.h index d10e54f03c09..848e5796400e 100644 --- a/include/linux/ipc_namespace.h +++ b/include/linux/ipc_namespace.h @@ -58,6 +58,7 @@ struct ipc_namespace { /* user_ns which owns the ipc ns */ struct user_namespace *user_ns; + struct ucounts *ucounts; struct ns_common ns; }; diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index f9df7dd2609a..e1d672186f00 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -28,6 +28,7 @@ enum ucount_type { UCOUNT_USER_NAMESPACES, UCOUNT_PID_NAMESPACES, UCOUNT_UTS_NAMESPACES, + UCOUNT_IPC_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/ipc/namespace.c b/ipc/namespace.c index d87e6baa1323..730914214135 100644 --- a/ipc/namespace.c +++ b/ipc/namespace.c @@ -16,39 +16,61 @@ #include "util.h" +static struct ucounts *inc_ipc_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_IPC_NAMESPACES); +} + +static void dec_ipc_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_IPC_NAMESPACES); +} + static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns, struct ipc_namespace *old_ns) { struct ipc_namespace *ns; + struct ucounts *ucounts; int err; + err = -ENFILE; + ucounts = inc_ipc_namespaces(user_ns); + if (!ucounts) + goto fail; + + err = -ENOMEM; ns = kmalloc(sizeof(struct ipc_namespace), GFP_KERNEL); if (ns == NULL) - return ERR_PTR(-ENOMEM); + goto fail_dec; err = ns_alloc_inum(&ns->ns); - if (err) { - kfree(ns); - return ERR_PTR(err); - } + if (err) + goto fail_free; ns->ns.ops = &ipcns_operations; atomic_set(&ns->count, 1); ns->user_ns = get_user_ns(user_ns); + ns->ucounts = ucounts; err = mq_init_ns(ns); - if (err) { - put_user_ns(ns->user_ns); - ns_free_inum(&ns->ns); - kfree(ns); - return ERR_PTR(err); - } + if (err) + goto fail_put; sem_init_ns(ns); msg_init_ns(ns); shm_init_ns(ns); return ns; + +fail_put: + put_user_ns(ns->user_ns); + ns_free_inum(&ns->ns); +fail_free: + kfree(ns); +fail_dec: + dec_ipc_namespaces(ucounts); +fail: + return ERR_PTR(err); } struct ipc_namespace *copy_ipcs(unsigned long flags, @@ -96,6 +118,7 @@ static void free_ipc_ns(struct ipc_namespace *ns) msg_exit_ns(ns); shm_exit_ns(ns); + dec_ipc_namespaces(ns->ucounts); put_user_ns(ns->user_ns); ns_free_inum(&ns->ns); kfree(ns); diff --git a/kernel/ucount.c b/kernel/ucount.c index 866850e2eb92..fbab75424da6 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -70,6 +70,7 @@ static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_user_namespaces"), UCOUNT_ENTRY("max_pid_namespaces"), UCOUNT_ENTRY("max_uts_namespaces"), + UCOUNT_ENTRY("max_ipc_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ -- cgit v1.2.3-59-g8ed1b From d08311dd6fd8444e39710dd2fb97562895aed8fa Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:25:30 -0500 Subject: cgroupns: Add a limit on the number of cgroup namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/cgroup.h | 1 + include/linux/user_namespace.h | 1 + kernel/cgroup.c | 18 ++++++++++++++++++ kernel/ucount.c | 1 + 4 files changed, 21 insertions(+) diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h index 984f73b719a9..1ed92812785a 100644 --- a/include/linux/cgroup.h +++ b/include/linux/cgroup.h @@ -621,6 +621,7 @@ struct cgroup_namespace { atomic_t count; struct ns_common ns; struct user_namespace *user_ns; + struct ucounts *ucounts; struct css_set *root_cset; }; diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index e1d672186f00..d067f0d3038e 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -29,6 +29,7 @@ enum ucount_type { UCOUNT_PID_NAMESPACES, UCOUNT_UTS_NAMESPACES, UCOUNT_IPC_NAMESPACES, + UCOUNT_CGROUP_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/kernel/cgroup.c b/kernel/cgroup.c index d1c51b7f5221..e9e4427fec46 100644 --- a/kernel/cgroup.c +++ b/kernel/cgroup.c @@ -6295,6 +6295,16 @@ void cgroup_sk_free(struct sock_cgroup_data *skcd) /* cgroup namespaces */ +static struct ucounts *inc_cgroup_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_CGROUP_NAMESPACES); +} + +static void dec_cgroup_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_CGROUP_NAMESPACES); +} + static struct cgroup_namespace *alloc_cgroup_ns(void) { struct cgroup_namespace *new_ns; @@ -6316,6 +6326,7 @@ static struct cgroup_namespace *alloc_cgroup_ns(void) void free_cgroup_ns(struct cgroup_namespace *ns) { put_css_set(ns->root_cset); + dec_cgroup_namespaces(ns->ucounts); put_user_ns(ns->user_ns); ns_free_inum(&ns->ns); kfree(ns); @@ -6327,6 +6338,7 @@ struct cgroup_namespace *copy_cgroup_ns(unsigned long flags, struct cgroup_namespace *old_ns) { struct cgroup_namespace *new_ns; + struct ucounts *ucounts; struct css_set *cset; BUG_ON(!old_ns); @@ -6340,6 +6352,10 @@ struct cgroup_namespace *copy_cgroup_ns(unsigned long flags, if (!ns_capable(user_ns, CAP_SYS_ADMIN)) return ERR_PTR(-EPERM); + ucounts = inc_cgroup_namespaces(user_ns); + if (!ucounts) + return ERR_PTR(-ENFILE); + /* It is not safe to take cgroup_mutex here */ spin_lock_irq(&css_set_lock); cset = task_css_set(current); @@ -6349,10 +6365,12 @@ struct cgroup_namespace *copy_cgroup_ns(unsigned long flags, new_ns = alloc_cgroup_ns(); if (IS_ERR(new_ns)) { put_css_set(cset); + dec_cgroup_namespaces(ucounts); return new_ns; } new_ns->user_ns = get_user_ns(user_ns); + new_ns->ucounts = ucounts; new_ns->root_cset = cset; return new_ns; diff --git a/kernel/ucount.c b/kernel/ucount.c index fbab75424da6..335cc5d2cdd7 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -71,6 +71,7 @@ static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_pid_namespaces"), UCOUNT_ENTRY("max_uts_namespaces"), UCOUNT_ENTRY("max_ipc_namespaces"), + UCOUNT_ENTRY("max_cgroup_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ -- cgit v1.2.3-59-g8ed1b From 703286608a220d53584cca5986aad5305eec75ed Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:33:23 -0500 Subject: netns: Add a limit on the number of net namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 1 + include/net/net_namespace.h | 1 + kernel/ucount.c | 1 + net/core/net_namespace.c | 22 +++++++++++++++++++++- 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index d067f0d3038e..c6bc980b06a9 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -29,6 +29,7 @@ enum ucount_type { UCOUNT_PID_NAMESPACES, UCOUNT_UTS_NAMESPACES, UCOUNT_IPC_NAMESPACES, + UCOUNT_NET_NAMESPACES, UCOUNT_CGROUP_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index 0933c7455a30..fc4f757107df 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -60,6 +60,7 @@ struct net { struct list_head exit_list; /* Use only net_mutex */ struct user_namespace *user_ns; /* Owning user namespace */ + struct ucounts *ucounts; spinlock_t nsid_lock; struct idr netns_ids; diff --git a/kernel/ucount.c b/kernel/ucount.c index 335cc5d2cdd7..205f1a07faac 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -71,6 +71,7 @@ static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_pid_namespaces"), UCOUNT_ENTRY("max_uts_namespaces"), UCOUNT_ENTRY("max_ipc_namespaces"), + UCOUNT_ENTRY("max_net_namespaces"), UCOUNT_ENTRY("max_cgroup_namespaces"), { } }; diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 2c2eb1b629b1..3e2812aeceb7 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -266,6 +266,16 @@ struct net *get_net_ns_by_id(struct net *net, int id) return peer; } +static struct ucounts *inc_net_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_NET_NAMESPACES); +} + +static void dec_net_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_NET_NAMESPACES); +} + /* * setup_net runs the initializers for the network namespace object. */ @@ -351,19 +361,27 @@ void net_drop_ns(void *p) struct net *copy_net_ns(unsigned long flags, struct user_namespace *user_ns, struct net *old_net) { + struct ucounts *ucounts; struct net *net; int rv; if (!(flags & CLONE_NEWNET)) return get_net(old_net); + ucounts = inc_net_namespaces(user_ns); + if (!ucounts) + return ERR_PTR(-ENFILE); + net = net_alloc(); - if (!net) + if (!net) { + dec_net_namespaces(ucounts); return ERR_PTR(-ENOMEM); + } get_user_ns(user_ns); mutex_lock(&net_mutex); + net->ucounts = ucounts; rv = setup_net(net, user_ns); if (rv == 0) { rtnl_lock(); @@ -372,6 +390,7 @@ struct net *copy_net_ns(unsigned long flags, } mutex_unlock(&net_mutex); if (rv < 0) { + dec_net_namespaces(ucounts); put_user_ns(user_ns); net_drop_ns(net); return ERR_PTR(rv); @@ -444,6 +463,7 @@ static void cleanup_net(struct work_struct *work) /* Finally it is safe to free my network namespace structure */ list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) { list_del_init(&net->exit_list); + dec_net_namespaces(net->ucounts); put_user_ns(net->user_ns); net_drop_ns(net); } -- cgit v1.2.3-59-g8ed1b From 537f7ccb396804c6d0057b93ba8eb104ba44f851 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:37:37 -0500 Subject: mntns: Add a limit on the number of mount namespaces. v2: Fixed the very obvious lack of setting ucounts on struct mnt_ns reported by Andrei Vagin, and the kbuild test report. Reported-by: Andrei Vagin Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- fs/mount.h | 1 + fs/namespace.c | 22 +++++++++++++++++++++- include/linux/user_namespace.h | 1 + kernel/ucount.c | 1 + 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/fs/mount.h b/fs/mount.h index 14db05d424f7..e037981d8351 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -10,6 +10,7 @@ struct mnt_namespace { struct mount * root; struct list_head list; struct user_namespace *user_ns; + struct ucounts *ucounts; u64 seq; /* Sequence number to prevent loops */ wait_queue_head_t poll; u64 event; diff --git a/fs/namespace.c b/fs/namespace.c index 7bb2cda3bfef..491b8f3e4c9a 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2719,9 +2719,20 @@ dput_out: return retval; } +static struct ucounts *inc_mnt_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_MNT_NAMESPACES); +} + +static void dec_mnt_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_MNT_NAMESPACES); +} + static void free_mnt_ns(struct mnt_namespace *ns) { ns_free_inum(&ns->ns); + dec_mnt_namespaces(ns->ucounts); put_user_ns(ns->user_ns); kfree(ns); } @@ -2738,14 +2749,22 @@ static atomic64_t mnt_ns_seq = ATOMIC64_INIT(1); static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *user_ns) { struct mnt_namespace *new_ns; + struct ucounts *ucounts; int ret; + ucounts = inc_mnt_namespaces(user_ns); + if (!ucounts) + return ERR_PTR(-ENFILE); + new_ns = kmalloc(sizeof(struct mnt_namespace), GFP_KERNEL); - if (!new_ns) + if (!new_ns) { + dec_mnt_namespaces(ucounts); return ERR_PTR(-ENOMEM); + } ret = ns_alloc_inum(&new_ns->ns); if (ret) { kfree(new_ns); + dec_mnt_namespaces(ucounts); return ERR_PTR(ret); } new_ns->ns.ops = &mntns_operations; @@ -2756,6 +2775,7 @@ static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *user_ns) init_waitqueue_head(&new_ns->poll); new_ns->event = 0; new_ns->user_ns = get_user_ns(user_ns); + new_ns->ucounts = ucounts; return new_ns; } diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index c6bc980b06a9..30ffe10cda18 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -30,6 +30,7 @@ enum ucount_type { UCOUNT_UTS_NAMESPACES, UCOUNT_IPC_NAMESPACES, UCOUNT_NET_NAMESPACES, + UCOUNT_MNT_NAMESPACES, UCOUNT_CGROUP_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/kernel/ucount.c b/kernel/ucount.c index 205f1a07faac..9d20d5dd298a 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -72,6 +72,7 @@ static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_uts_namespaces"), UCOUNT_ENTRY("max_ipc_namespaces"), UCOUNT_ENTRY("max_net_namespaces"), + UCOUNT_ENTRY("max_mnt_namespaces"), UCOUNT_ENTRY("max_cgroup_namespaces"), { } }; -- cgit v1.2.3-59-g8ed1b From 9c722e406a64db181f6a7b53a19a58fe61501f99 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Thu, 22 Sep 2016 12:52:03 -0500 Subject: userns; Document per user per user namespace limits. Signed-off-by: "Eric W. Biederman" --- Documentation/sysctl/README | 1 + Documentation/sysctl/user.txt | 66 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 Documentation/sysctl/user.txt diff --git a/Documentation/sysctl/README b/Documentation/sysctl/README index 8c3306e01d52..91f54ffa0077 100644 --- a/Documentation/sysctl/README +++ b/Documentation/sysctl/README @@ -69,6 +69,7 @@ proc/ sunrpc/ SUN Remote Procedure Call (NFS) vm/ memory management tuning buffer and cache management +user/ Per user per user namespace limits These are the subdirs I have on my system. There might be more or other subdirs in another setup. If you see another dir, I'd diff --git a/Documentation/sysctl/user.txt b/Documentation/sysctl/user.txt new file mode 100644 index 000000000000..1291c498f78f --- /dev/null +++ b/Documentation/sysctl/user.txt @@ -0,0 +1,66 @@ +Documentation for /proc/sys/user/* kernel version 4.9.0 + (c) 2016 Eric Biederman + +============================================================== + +This file contains the documetation for the sysctl files in +/proc/sys/user. + +The files in this directory can be used to override the default +limits on the number of namespaces and other objects that have +per user per user namespace limits. + +The primary purpose of these limits is to stop programs that +malfunction and attempt to create a ridiculous number of objects, +before the malfunction becomes a system wide problem. It is the +intention that the defaults of these limits are set high enough that +no program in normal operation should run into these limits. + +The creation of per user per user namespace objects are charged to +the user in the user namespace who created the object and +verified to be below the per user limit in that user namespace. + +The creation of objects is also charged to all of the users +who created user namespaces the creation of the object happens +in (user namespaces can be nested) and verified to be below the per user +limits in the user namespaces of those users. + +This recursive counting of created objects ensures that creating a +user namespace does not allow a user to escape their current limits. + +Currently, these files are in /proc/sys/user: + +- max_cgroup_namespaces + + The maximum number of cgroup namespaces that any user in the current + user namespace may create. + +- max_ipc_namespaces + + The maximum number of ipc namespaces that any user in the current + user namespace may create. + +- max_mnt_namespaces + + The maximum number of mount namespaces that any user in the current + user namespace may create. + +- max_net_namespaces + + The maximum number of network namespaces that any user in the + current user namespace may create. + +- max_pid_namespaces + + The maximum number of pid namespaces that any user in the current + user namespace may create. + +- max_user_namespaces + + The maximum number of user namespaces that any user in the current + user namespace may create. + +- max_uts_namespaces + + The maximum number of user namespaces that any user in the current + user namespace may create. -- cgit v1.2.3-59-g8ed1b From df75e7748bae1c7098bfa358485389b897f71305 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Thu, 22 Sep 2016 13:08:36 -0500 Subject: userns: When the per user per user namespace limit is reached return ENOSPC The current error codes returned when a the per user per user namespace limit are hit (EINVAL, EUSERS, and ENFILE) are wrong. I asked for advice on linux-api and it we made clear that those were the wrong error code, but a correct effor code was not suggested. The best general error code I have found for hitting a resource limit is ENOSPC. It is not perfect but as it is unambiguous it will serve until someone comes up with a better error code. Signed-off-by: "Eric W. Biederman" --- fs/namespace.c | 2 +- ipc/namespace.c | 2 +- kernel/cgroup.c | 2 +- kernel/pid_namespace.c | 2 +- kernel/user_namespace.c | 2 +- kernel/utsname.c | 2 +- net/core/net_namespace.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 491b8f3e4c9a..cf2cc234c8b4 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2754,7 +2754,7 @@ static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *user_ns) ucounts = inc_mnt_namespaces(user_ns); if (!ucounts) - return ERR_PTR(-ENFILE); + return ERR_PTR(-ENOSPC); new_ns = kmalloc(sizeof(struct mnt_namespace), GFP_KERNEL); if (!new_ns) { diff --git a/ipc/namespace.c b/ipc/namespace.c index 730914214135..fab727d9fe09 100644 --- a/ipc/namespace.c +++ b/ipc/namespace.c @@ -33,7 +33,7 @@ static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns, struct ucounts *ucounts; int err; - err = -ENFILE; + err = -ENOSPC; ucounts = inc_ipc_namespaces(user_ns); if (!ucounts) goto fail; diff --git a/kernel/cgroup.c b/kernel/cgroup.c index e9e4427fec46..f1dd4b076210 100644 --- a/kernel/cgroup.c +++ b/kernel/cgroup.c @@ -6354,7 +6354,7 @@ struct cgroup_namespace *copy_cgroup_ns(unsigned long flags, ucounts = inc_cgroup_namespaces(user_ns); if (!ucounts) - return ERR_PTR(-ENFILE); + return ERR_PTR(-ENOSPC); /* It is not safe to take cgroup_mutex here */ spin_lock_irq(&css_set_lock); diff --git a/kernel/pid_namespace.c b/kernel/pid_namespace.c index 30a7f3351932..7542b28cc929 100644 --- a/kernel/pid_namespace.c +++ b/kernel/pid_namespace.c @@ -98,7 +98,7 @@ static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns int i; int err; - err = -EINVAL; + err = -ENOSPC; if (level > MAX_PID_NS_LEVEL) goto out; ucounts = inc_pid_namespaces(user_ns); diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 0edafe305861..f2c5ba5505f1 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -76,7 +76,7 @@ int create_user_ns(struct cred *new) struct ucounts *ucounts; int ret, i; - ret = -EUSERS; + ret = -ENOSPC; if (parent_ns->level > 32) goto fail; diff --git a/kernel/utsname.c b/kernel/utsname.c index f3b0bb4ac3ba..35587b76faa3 100644 --- a/kernel/utsname.c +++ b/kernel/utsname.c @@ -49,7 +49,7 @@ static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns, struct ucounts *ucounts; int err; - err = -ENFILE; + err = -ENOSPC; ucounts = inc_uts_namespaces(user_ns); if (!ucounts) goto fail; diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 3e2812aeceb7..06af5d6a883c 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -370,7 +370,7 @@ struct net *copy_net_ns(unsigned long flags, ucounts = inc_net_namespaces(user_ns); if (!ucounts) - return ERR_PTR(-ENFILE); + return ERR_PTR(-ENOSPC); net = net_alloc(); if (!net) { -- cgit v1.2.3-59-g8ed1b From 208904793abad8892422edad0e712f2d939c496b Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 20 Apr 2016 12:01:39 -0500 Subject: devpts: Move parse_mount_options into fill_super Signed-off-by: "Eric W. Biederman" --- fs/devpts/inode.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index d116453b0276..f582a4be1a7a 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -395,6 +395,7 @@ static int devpts_fill_super(struct super_block *s, void *data, int silent) { struct inode *inode; + int error; s->s_iflags &= ~SB_I_NODEV; s->s_blocksize = 1024; @@ -403,10 +404,16 @@ devpts_fill_super(struct super_block *s, void *data, int silent) s->s_op = &devpts_sops; s->s_time_gran = 1; + error = -ENOMEM; s->s_fs_info = new_pts_fs_info(s); if (!s->s_fs_info) goto fail; + error = parse_mount_options(data, PARSE_MOUNT, &DEVPTS_SB(s)->mount_opts); + if (error) + goto fail; + + error = -ENOMEM; inode = new_inode(s); if (!inode) goto fail; @@ -424,7 +431,7 @@ devpts_fill_super(struct super_block *s, void *data, int silent) pr_err("get root dentry failed\n"); fail: - return -ENOMEM; + return error; } /* @@ -437,13 +444,8 @@ static struct dentry *devpts_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { int error; - struct pts_mount_opts opts; struct super_block *s; - error = parse_mount_options(data, PARSE_MOUNT, &opts); - if (error) - return ERR_PTR(error); - s = sget(fs_type, NULL, set_anon_super, flags, NULL); if (IS_ERR(s)) return ERR_CAST(s); @@ -455,8 +457,6 @@ static struct dentry *devpts_mount(struct file_system_type *fs_type, s->s_flags |= MS_ACTIVE; } - memcpy(&(DEVPTS_SB(s))->mount_opts, &opts, sizeof(opts)); - error = mknod_ptmx(s); if (error) goto out_undo_sget; -- cgit v1.2.3-59-g8ed1b From 7dd17f713474504fa6d61d666e27b02e4a608abe Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Tue, 19 Apr 2016 17:51:04 -0500 Subject: devpts: Move the creation of /dev/pts/ptmx into fill_super The code makes more sense here and things are just clearer. Signed-off-by: "Eric W. Biederman" --- fs/devpts/inode.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index f582a4be1a7a..f3277f711b25 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -425,11 +425,19 @@ devpts_fill_super(struct super_block *s, void *data, int silent) set_nlink(inode, 2); s->s_root = d_make_root(inode); - if (s->s_root) - return 0; + if (!s->s_root) { + pr_err("get root dentry failed\n"); + goto fail; + } - pr_err("get root dentry failed\n"); + error = mknod_ptmx(s); + if (error) + goto fail_dput; + return 0; +fail_dput: + dput(s->s_root); + s->s_root = NULL; fail: return error; } @@ -456,11 +464,6 @@ static struct dentry *devpts_mount(struct file_system_type *fs_type, goto out_undo_sget; s->s_flags |= MS_ACTIVE; } - - error = mknod_ptmx(s); - if (error) - goto out_undo_sget; - return dget(s->s_root); out_undo_sget: -- cgit v1.2.3-59-g8ed1b From ec0a9ba6f201bbb4801344aa11c5d13c1ca27675 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Tue, 19 Apr 2016 17:52:53 -0500 Subject: devpts: Simplify devpts_mount by using mount_nodev Now that all of the work of setting up a superblock has been moved to devpts_fill_super simplify devpts_mount by calling mount_nodev instead of rolling mount_nodev by hand. Signed-off-by: "Eric W. Biederman" --- fs/devpts/inode.c | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index f3277f711b25..5e216749bd96 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -451,24 +451,7 @@ fail: static struct dentry *devpts_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { - int error; - struct super_block *s; - - s = sget(fs_type, NULL, set_anon_super, flags, NULL); - if (IS_ERR(s)) - return ERR_CAST(s); - - if (!s->s_root) { - error = devpts_fill_super(s, data, flags & MS_SILENT ? 1 : 0); - if (error) - goto out_undo_sget; - s->s_flags |= MS_ACTIVE; - } - return dget(s->s_root); - -out_undo_sget: - deactivate_locked_super(s); - return ERR_PTR(error); + return mount_nodev(fs_type, flags, data, devpts_fill_super); } static void devpts_kill_sb(struct super_block *sb) -- cgit v1.2.3-59-g8ed1b From 0d126a7ff77a02e88b6bf37a726abf8990226bf4 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Tue, 22 Dec 2015 17:39:18 -0600 Subject: devpts: Make devpts_kill_sb safe if fsi is NULL Signed-off-by: "Eric W. Biederman" --- fs/devpts/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 5e216749bd96..2b0f24cb7d54 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -458,7 +458,8 @@ static void devpts_kill_sb(struct super_block *sb) { struct pts_fs_info *fsi = DEVPTS_SB(sb); - ida_destroy(&fsi->allocated_ptys); + if (fsi) + ida_destroy(&fsi->allocated_ptys); kfree(fsi); kill_litter_super(sb); } -- cgit v1.2.3-59-g8ed1b From 985e5d856cbcfc17a6646740f2200eb625c76e89 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 20 Apr 2016 12:02:09 -0500 Subject: devpts: Remove sync_filesystems devpts does not and never will have anything to sync so don't bother calling sync_filesystems on remount. Signed-off-by: "Eric W. Biederman" --- fs/devpts/inode.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 2b0f24cb7d54..d08971e8eaae 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -336,7 +336,6 @@ static int devpts_remount(struct super_block *sb, int *flags, char *data) struct pts_fs_info *fsi = DEVPTS_SB(sb); struct pts_mount_opts *opts = &fsi->mount_opts; - sync_filesystem(sb); err = parse_mount_options(data, PARSE_REMOUNT, opts); /* -- cgit v1.2.3-59-g8ed1b From 93f0a88bd4ad99a515f500a09f4a489ff03073eb Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Tue, 8 Dec 2015 00:36:51 -0600 Subject: devpts: Change the owner of /dev/pts/ptmx to the mounter of /dev/pts In 99.99% of the cases only root in a user namespace can mount /dev/pts and in those cases the owner of /dev/pts/ptmx will remain root.root In the oddball case where someone else has CAP_SYS_ADMIN this code modifies the /dev/pts mount code to use current_fsuid and current_fsgid as the values to use when creating the /dev/ptmx inode. As is done when any other file is created. This is a code simplification, and it allows running without a root user entirely. Signed-off-by: "Eric W. Biederman" --- fs/devpts/inode.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index d08971e8eaae..154cc45c19e8 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -272,13 +272,8 @@ static int mknod_ptmx(struct super_block *sb) struct dentry *root = sb->s_root; struct pts_fs_info *fsi = DEVPTS_SB(sb); struct pts_mount_opts *opts = &fsi->mount_opts; - kuid_t root_uid; - kgid_t root_gid; - - root_uid = make_kuid(current_user_ns(), 0); - root_gid = make_kgid(current_user_ns(), 0); - if (!uid_valid(root_uid) || !gid_valid(root_gid)) - return -EINVAL; + kuid_t ptmx_uid = current_fsuid(); + kgid_t ptmx_gid = current_fsgid(); inode_lock(d_inode(root)); @@ -309,8 +304,8 @@ static int mknod_ptmx(struct super_block *sb) mode = S_IFCHR|opts->ptmxmode; init_special_inode(inode, mode, MKDEV(TTYAUX_MAJOR, 2)); - inode->i_uid = root_uid; - inode->i_gid = root_gid; + inode->i_uid = ptmx_uid; + inode->i_gid = ptmx_gid; d_add(dentry, inode); -- cgit v1.2.3-59-g8ed1b From bcac25a58bfc6bd79191ac5d7afb49bea96da8c9 Mon Sep 17 00:00:00 2001 From: Andrey Vagin Date: Tue, 6 Sep 2016 00:47:13 -0700 Subject: kernel: add a helper to get an owning user namespace for a namespace Return -EPERM if an owning user namespace is outside of a process current user namespace. v2: In a first version ns_get_owner returned ENOENT for init_user_ns. This special cases was removed from this version. There is nothing outside of init_user_ns, so we can return EPERM. v3: rename ns->get_owner() to ns->owner(). get_* usually means that it grabs a reference. Acked-by: Serge Hallyn Signed-off-by: Andrei Vagin Signed-off-by: Eric W. Biederman --- fs/namespace.c | 6 ++++++ include/linux/proc_ns.h | 1 + include/linux/user_namespace.h | 7 +++++++ ipc/namespace.c | 6 ++++++ kernel/cgroup.c | 6 ++++++ kernel/pid_namespace.c | 6 ++++++ kernel/user_namespace.c | 24 ++++++++++++++++++++++++ kernel/utsname.c | 6 ++++++ net/core/net_namespace.c | 6 ++++++ 9 files changed, 68 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index 7bb2cda3bfef..fea56f310547 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3348,10 +3348,16 @@ static int mntns_install(struct nsproxy *nsproxy, struct ns_common *ns) return 0; } +static struct user_namespace *mntns_owner(struct ns_common *ns) +{ + return to_mnt_ns(ns)->user_ns; +} + const struct proc_ns_operations mntns_operations = { .name = "mnt", .type = CLONE_NEWNS, .get = mntns_get, .put = mntns_put, .install = mntns_install, + .owner = mntns_owner, }; diff --git a/include/linux/proc_ns.h b/include/linux/proc_ns.h index de0e7719d4c5..ca85a4348ffc 100644 --- a/include/linux/proc_ns.h +++ b/include/linux/proc_ns.h @@ -18,6 +18,7 @@ struct proc_ns_operations { struct ns_common *(*get)(struct task_struct *task); void (*put)(struct ns_common *ns); int (*install)(struct nsproxy *nsproxy, struct ns_common *ns); + struct user_namespace *(*owner)(struct ns_common *ns); }; extern const struct proc_ns_operations netns_operations; diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 9217169c64cb..190cf0760815 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -73,6 +73,8 @@ extern ssize_t proc_setgroups_write(struct file *, const char __user *, size_t, extern int proc_setgroups_show(struct seq_file *m, void *v); extern bool userns_may_setgroups(const struct user_namespace *ns); extern bool current_in_userns(const struct user_namespace *target_ns); + +struct ns_common *ns_get_owner(struct ns_common *ns); #else static inline struct user_namespace *get_user_ns(struct user_namespace *ns) @@ -106,6 +108,11 @@ static inline bool current_in_userns(const struct user_namespace *target_ns) { return true; } + +static inline struct ns_common *ns_get_owner(struct ns_common *ns) +{ + return ERR_PTR(-EPERM); +} #endif #endif /* _LINUX_USER_H */ diff --git a/ipc/namespace.c b/ipc/namespace.c index d87e6baa1323..578d93be619d 100644 --- a/ipc/namespace.c +++ b/ipc/namespace.c @@ -165,10 +165,16 @@ static int ipcns_install(struct nsproxy *nsproxy, struct ns_common *new) return 0; } +static struct user_namespace *ipcns_owner(struct ns_common *ns) +{ + return to_ipc_ns(ns)->user_ns; +} + const struct proc_ns_operations ipcns_operations = { .name = "ipc", .type = CLONE_NEWIPC, .get = ipcns_get, .put = ipcns_put, .install = ipcns_install, + .owner = ipcns_owner, }; diff --git a/kernel/cgroup.c b/kernel/cgroup.c index d1c51b7f5221..86b0e8b16426 100644 --- a/kernel/cgroup.c +++ b/kernel/cgroup.c @@ -6403,12 +6403,18 @@ static void cgroupns_put(struct ns_common *ns) put_cgroup_ns(to_cg_ns(ns)); } +static struct user_namespace *cgroupns_owner(struct ns_common *ns) +{ + return to_cg_ns(ns)->user_ns; +} + const struct proc_ns_operations cgroupns_operations = { .name = "cgroup", .type = CLONE_NEWCGROUP, .get = cgroupns_get, .put = cgroupns_put, .install = cgroupns_install, + .owner = cgroupns_owner, }; static __init int cgroup_namespaces_init(void) diff --git a/kernel/pid_namespace.c b/kernel/pid_namespace.c index a65ba137fd15..c02d744225e1 100644 --- a/kernel/pid_namespace.c +++ b/kernel/pid_namespace.c @@ -388,12 +388,18 @@ static int pidns_install(struct nsproxy *nsproxy, struct ns_common *ns) return 0; } +static struct user_namespace *pidns_owner(struct ns_common *ns) +{ + return to_pid_ns(ns)->user_ns; +} + const struct proc_ns_operations pidns_operations = { .name = "pid", .type = CLONE_NEWPID, .get = pidns_get, .put = pidns_put, .install = pidns_install, + .owner = pidns_owner, }; static __init int pid_namespaces_init(void) diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 68f594212759..0ef683a03c20 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -1004,12 +1004,36 @@ static int userns_install(struct nsproxy *nsproxy, struct ns_common *ns) return commit_creds(cred); } +struct ns_common *ns_get_owner(struct ns_common *ns) +{ + struct user_namespace *my_user_ns = current_user_ns(); + struct user_namespace *owner, *p; + + /* See if the owner is in the current user namespace */ + owner = p = ns->ops->owner(ns); + for (;;) { + if (!p) + return ERR_PTR(-EPERM); + if (p == my_user_ns) + break; + p = p->parent; + } + + return &get_user_ns(owner)->ns; +} + +static struct user_namespace *userns_owner(struct ns_common *ns) +{ + return to_user_ns(ns)->parent; +} + const struct proc_ns_operations userns_operations = { .name = "user", .type = CLONE_NEWUSER, .get = userns_get, .put = userns_put, .install = userns_install, + .owner = userns_owner, }; static __init int user_namespaces_init(void) diff --git a/kernel/utsname.c b/kernel/utsname.c index 831ea7108232..e1211a8a5c18 100644 --- a/kernel/utsname.c +++ b/kernel/utsname.c @@ -130,10 +130,16 @@ static int utsns_install(struct nsproxy *nsproxy, struct ns_common *new) return 0; } +static struct user_namespace *utsns_owner(struct ns_common *ns) +{ + return to_uts_ns(ns)->user_ns; +} + const struct proc_ns_operations utsns_operations = { .name = "uts", .type = CLONE_NEWUTS, .get = utsns_get, .put = utsns_put, .install = utsns_install, + .owner = utsns_owner, }; diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 2c2eb1b629b1..861efa34f08c 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -996,11 +996,17 @@ static int netns_install(struct nsproxy *nsproxy, struct ns_common *ns) return 0; } +static struct user_namespace *netns_owner(struct ns_common *ns) +{ + return to_net_ns(ns)->user_ns; +} + const struct proc_ns_operations netns_operations = { .name = "net", .type = CLONE_NEWNET, .get = netns_get, .put = netns_put, .install = netns_install, + .owner = netns_owner, }; #endif -- cgit v1.2.3-59-g8ed1b From 6786741dbf99e44fb0c0ed85a37582b8a26f1c3b Mon Sep 17 00:00:00 2001 From: Andrey Vagin Date: Tue, 6 Sep 2016 00:47:14 -0700 Subject: nsfs: add ioctl to get an owning user namespace for ns file descriptor Each namespace has an owning user namespace and now there is not way to discover these relationships. Understending namespaces relationships allows to answer the question: what capability does process X have to perform operations on a resource governed by namespace Y? After a long discussion, Eric W. Biederman proposed to use ioctl-s for this purpose. The NS_GET_USERNS ioctl returns a file descriptor to an owning user namespace. It returns EPERM if a target namespace is outside of a current user namespace. v2: rename parent to relative v3: Add a missing mntput when returning -EAGAIN --EWB Acked-by: Serge Hallyn Link: https://lkml.org/lkml/2016/7/6/158 Signed-off-by: Andrei Vagin Signed-off-by: Eric W. Biederman --- fs/nsfs.c | 96 ++++++++++++++++++++++++++++++++++++++++------- include/uapi/linux/nsfs.h | 11 ++++++ 2 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 include/uapi/linux/nsfs.h diff --git a/fs/nsfs.c b/fs/nsfs.c index 8f20d6016e20..3887da470f7e 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -5,11 +5,16 @@ #include #include #include +#include +#include static struct vfsmount *nsfs_mnt; +static long ns_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg); static const struct file_operations ns_file_operations = { .llseek = no_llseek, + .unlocked_ioctl = ns_ioctl, }; static char *ns_dname(struct dentry *dentry, char *buffer, int buflen) @@ -44,22 +49,14 @@ static void nsfs_evict(struct inode *inode) ns->ops->put(ns); } -void *ns_get_path(struct path *path, struct task_struct *task, - const struct proc_ns_operations *ns_ops) +static void *__ns_get_path(struct path *path, struct ns_common *ns) { struct vfsmount *mnt = mntget(nsfs_mnt); struct qstr qname = { .name = "", }; struct dentry *dentry; struct inode *inode; - struct ns_common *ns; unsigned long d; -again: - ns = ns_ops->get(task); - if (!ns) { - mntput(mnt); - return ERR_PTR(-ENOENT); - } rcu_read_lock(); d = atomic_long_read(&ns->stashed); if (!d) @@ -68,7 +65,7 @@ again: if (!lockref_get_not_dead(&dentry->d_lockref)) goto slow; rcu_read_unlock(); - ns_ops->put(ns); + ns->ops->put(ns); got_it: path->mnt = mnt; path->dentry = dentry; @@ -77,7 +74,7 @@ slow: rcu_read_unlock(); inode = new_inode_pseudo(mnt->mnt_sb); if (!inode) { - ns_ops->put(ns); + ns->ops->put(ns); mntput(mnt); return ERR_PTR(-ENOMEM); } @@ -95,17 +92,90 @@ slow: return ERR_PTR(-ENOMEM); } d_instantiate(dentry, inode); - dentry->d_fsdata = (void *)ns_ops; + dentry->d_fsdata = (void *)ns->ops; d = atomic_long_cmpxchg(&ns->stashed, 0, (unsigned long)dentry); if (d) { d_delete(dentry); /* make sure ->d_prune() does nothing */ dput(dentry); + mntput(mnt); cpu_relax(); - goto again; + return ERR_PTR(-EAGAIN); } goto got_it; } +void *ns_get_path(struct path *path, struct task_struct *task, + const struct proc_ns_operations *ns_ops) +{ + struct ns_common *ns; + void *ret; + +again: + ns = ns_ops->get(task); + if (!ns) + return ERR_PTR(-ENOENT); + + ret = __ns_get_path(path, ns); + if (IS_ERR(ret) && PTR_ERR(ret) == -EAGAIN) + goto again; + return ret; +} + +static int open_related_ns(struct ns_common *ns, + struct ns_common *(*get_ns)(struct ns_common *ns)) +{ + struct path path = {}; + struct file *f; + void *err; + int fd; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + return fd; + + while (1) { + struct ns_common *relative; + + relative = get_ns(ns); + if (IS_ERR(relative)) { + put_unused_fd(fd); + return PTR_ERR(relative); + } + + err = __ns_get_path(&path, relative); + if (IS_ERR(err) && PTR_ERR(err) == -EAGAIN) + continue; + break; + } + if (IS_ERR(err)) { + put_unused_fd(fd); + return PTR_ERR(err); + } + + f = dentry_open(&path, O_RDONLY, current_cred()); + path_put(&path); + if (IS_ERR(f)) { + put_unused_fd(fd); + fd = PTR_ERR(f); + } else + fd_install(fd, f); + + return fd; +} + +static long ns_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg) +{ + struct ns_common *ns = get_proc_ns(file_inode(filp)); + + switch (ioctl) { + case NS_GET_USERNS: + return open_related_ns(ns, ns_get_owner); + default: + return -ENOTTY; + } +} + int ns_get_name(char *buf, size_t size, struct task_struct *task, const struct proc_ns_operations *ns_ops) { diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h new file mode 100644 index 000000000000..5cacd5c1b5d7 --- /dev/null +++ b/include/uapi/linux/nsfs.h @@ -0,0 +1,11 @@ +#ifndef __LINUX_NSFS_H +#define __LINUX_NSFS_H + +#include + +#define NSIO 0xb7 + +/* Returns a file descriptor that refers to an owning user namespace */ +#define NS_GET_USERNS _IO(NSIO, 0x1) + +#endif /* __LINUX_NSFS_H */ -- cgit v1.2.3-59-g8ed1b From a7306ed8d94af729ecef8b6e37506a1c6fc14788 Mon Sep 17 00:00:00 2001 From: Andrey Vagin Date: Tue, 6 Sep 2016 00:47:15 -0700 Subject: nsfs: add ioctl to get a parent namespace Pid and user namepaces are hierarchical. There is no way to discover parent-child relationships. In a future we will use this interface to dump and restore nested namespaces. Acked-by: Serge Hallyn Signed-off-by: Andrei Vagin Signed-off-by: Eric W. Biederman --- fs/nsfs.c | 4 ++++ include/linux/proc_ns.h | 1 + include/uapi/linux/nsfs.h | 2 ++ kernel/pid_namespace.c | 19 +++++++++++++++++++ kernel/user_namespace.c | 1 + 5 files changed, 27 insertions(+) diff --git a/fs/nsfs.c b/fs/nsfs.c index 3887da470f7e..fb7b397a1297 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -171,6 +171,10 @@ static long ns_ioctl(struct file *filp, unsigned int ioctl, switch (ioctl) { case NS_GET_USERNS: return open_related_ns(ns, ns_get_owner); + case NS_GET_PARENT: + if (!ns->ops->get_parent) + return -EINVAL; + return open_related_ns(ns, ns->ops->get_parent); default: return -ENOTTY; } diff --git a/include/linux/proc_ns.h b/include/linux/proc_ns.h index ca85a4348ffc..12cb8bd81d2d 100644 --- a/include/linux/proc_ns.h +++ b/include/linux/proc_ns.h @@ -19,6 +19,7 @@ struct proc_ns_operations { void (*put)(struct ns_common *ns); int (*install)(struct nsproxy *nsproxy, struct ns_common *ns); struct user_namespace *(*owner)(struct ns_common *ns); + struct ns_common *(*get_parent)(struct ns_common *ns); }; extern const struct proc_ns_operations netns_operations; diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h index 5cacd5c1b5d7..3af617230d1b 100644 --- a/include/uapi/linux/nsfs.h +++ b/include/uapi/linux/nsfs.h @@ -7,5 +7,7 @@ /* Returns a file descriptor that refers to an owning user namespace */ #define NS_GET_USERNS _IO(NSIO, 0x1) +/* Returns a file descriptor that refers to a parent namespace */ +#define NS_GET_PARENT _IO(NSIO, 0x2) #endif /* __LINUX_NSFS_H */ diff --git a/kernel/pid_namespace.c b/kernel/pid_namespace.c index c02d744225e1..4fa2d56a936c 100644 --- a/kernel/pid_namespace.c +++ b/kernel/pid_namespace.c @@ -388,6 +388,24 @@ static int pidns_install(struct nsproxy *nsproxy, struct ns_common *ns) return 0; } +static struct ns_common *pidns_get_parent(struct ns_common *ns) +{ + struct pid_namespace *active = task_active_pid_ns(current); + struct pid_namespace *pid_ns, *p; + + /* See if the parent is in the current namespace */ + pid_ns = p = to_pid_ns(ns)->parent; + for (;;) { + if (!p) + return ERR_PTR(-EPERM); + if (p == active) + break; + p = p->parent; + } + + return &get_pid_ns(pid_ns)->ns; +} + static struct user_namespace *pidns_owner(struct ns_common *ns) { return to_pid_ns(ns)->user_ns; @@ -400,6 +418,7 @@ const struct proc_ns_operations pidns_operations = { .put = pidns_put, .install = pidns_install, .owner = pidns_owner, + .get_parent = pidns_get_parent, }; static __init int pid_namespaces_init(void) diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 0ef683a03c20..a58a219b99c6 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -1034,6 +1034,7 @@ const struct proc_ns_operations userns_operations = { .put = userns_put, .install = userns_install, .owner = userns_owner, + .get_parent = ns_get_owner, }; static __init int user_namespaces_init(void) -- cgit v1.2.3-59-g8ed1b From 6ad92bf63e45f97e306da48cd1cbce6e4fef1e5d Mon Sep 17 00:00:00 2001 From: Andrey Vagin Date: Tue, 6 Sep 2016 00:47:16 -0700 Subject: tools/testing: add a test to check nsfs ioctl-s There are two new ioctl-s: One ioctl for the user namespace that owns a file descriptor. One ioctl for the parent namespace of a namespace file descriptor. The test checks that these ioctl-s works and that they handle a case when a target namespace is outside of the current process namespace. Signed-off-by: Andrei Vagin Signed-off-by: Eric W. Biederman --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/nsfs/Makefile | 12 +++++ tools/testing/selftests/nsfs/owner.c | 91 +++++++++++++++++++++++++++++++++++ tools/testing/selftests/nsfs/pidns.c | 78 ++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 tools/testing/selftests/nsfs/Makefile create mode 100644 tools/testing/selftests/nsfs/owner.c create mode 100644 tools/testing/selftests/nsfs/pidns.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index ff9e5f20a5a7..f770dba2a6f6 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -15,6 +15,7 @@ TARGETS += memory-hotplug TARGETS += mount TARGETS += mqueue TARGETS += net +TARGETS += nsfs TARGETS += powerpc TARGETS += pstore TARGETS += ptrace diff --git a/tools/testing/selftests/nsfs/Makefile b/tools/testing/selftests/nsfs/Makefile new file mode 100644 index 000000000000..2306054a901a --- /dev/null +++ b/tools/testing/selftests/nsfs/Makefile @@ -0,0 +1,12 @@ +TEST_PROGS := owner pidns + +CFLAGS := -Wall -Werror + +all: owner pidns +owner: owner.c +pidns: pidns.c + +clean: + $(RM) owner pidns + +include ../lib.mk diff --git a/tools/testing/selftests/nsfs/owner.c b/tools/testing/selftests/nsfs/owner.c new file mode 100644 index 000000000000..437205f8b714 --- /dev/null +++ b/tools/testing/selftests/nsfs/owner.c @@ -0,0 +1,91 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NSIO 0xb7 +#define NS_GET_USERNS _IO(NSIO, 0x1) + +#define pr_err(fmt, ...) \ + ({ \ + fprintf(stderr, "%s:%d:" fmt ": %m\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + 1; \ + }) + +int main(int argc, char *argvp[]) +{ + int pfd[2], ns, uns, init_uns; + struct stat st1, st2; + char path[128]; + pid_t pid; + char c; + + if (pipe(pfd)) + return 1; + + pid = fork(); + if (pid < 0) + return pr_err("fork"); + if (pid == 0) { + prctl(PR_SET_PDEATHSIG, SIGKILL); + if (unshare(CLONE_NEWUTS | CLONE_NEWUSER)) + return pr_err("unshare"); + close(pfd[0]); + close(pfd[1]); + while (1) + sleep(1); + return 0; + } + close(pfd[1]); + if (read(pfd[0], &c, 1) != 0) + return pr_err("Unable to read from pipe"); + close(pfd[0]); + + snprintf(path, sizeof(path), "/proc/%d/ns/uts", pid); + ns = open(path, O_RDONLY); + if (ns < 0) + return pr_err("Unable to open %s", path); + + uns = ioctl(ns, NS_GET_USERNS); + if (uns < 0) + return pr_err("Unable to get an owning user namespace"); + + if (fstat(uns, &st1)) + return pr_err("fstat"); + + snprintf(path, sizeof(path), "/proc/%d/ns/user", pid); + if (stat(path, &st2)) + return pr_err("stat"); + + if (st1.st_ino != st2.st_ino) + return pr_err("NS_GET_USERNS returned a wrong namespace"); + + init_uns = ioctl(uns, NS_GET_USERNS); + if (uns < 0) + return pr_err("Unable to get an owning user namespace"); + + if (ioctl(init_uns, NS_GET_USERNS) >= 0 || errno != EPERM) + return pr_err("Don't get EPERM"); + + if (unshare(CLONE_NEWUSER)) + return pr_err("unshare"); + + if (ioctl(ns, NS_GET_USERNS) >= 0 || errno != EPERM) + return pr_err("Don't get EPERM"); + if (ioctl(init_uns, NS_GET_USERNS) >= 0 || errno != EPERM) + return pr_err("Don't get EPERM"); + + kill(pid, SIGKILL); + wait(NULL); + return 0; +} diff --git a/tools/testing/selftests/nsfs/pidns.c b/tools/testing/selftests/nsfs/pidns.c new file mode 100644 index 000000000000..ae3a0d68e966 --- /dev/null +++ b/tools/testing/selftests/nsfs/pidns.c @@ -0,0 +1,78 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define pr_err(fmt, ...) \ + ({ \ + fprintf(stderr, "%s:%d:" fmt ": %m\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + 1; \ + }) + +#define NSIO 0xb7 +#define NS_GET_USERNS _IO(NSIO, 0x1) +#define NS_GET_PARENT _IO(NSIO, 0x2) + +#define __stack_aligned__ __attribute__((aligned(16))) +struct cr_clone_arg { + char stack[128] __stack_aligned__; + char stack_ptr[0]; +}; + +static int child(void *args) +{ + prctl(PR_SET_PDEATHSIG, SIGKILL); + while (1) + sleep(1); + exit(0); +} + +int main(int argc, char *argv[]) +{ + char *ns_strs[] = {"pid", "user"}; + char path[] = "/proc/0123456789/ns/pid"; + struct cr_clone_arg ca; + struct stat st1, st2; + int ns, pns, i; + pid_t pid; + + pid = clone(child, ca.stack_ptr, CLONE_NEWUSER | CLONE_NEWPID | SIGCHLD, NULL); + if (pid < 0) + return pr_err("clone"); + + for (i = 0; i < 2; i++) { + snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, ns_strs[i]); + ns = open(path, O_RDONLY); + if (ns < 0) + return pr_err("Unable to open %s", path); + + pns = ioctl(ns, NS_GET_PARENT); + if (pns < 0) + return pr_err("Unable to get a parent pidns"); + + snprintf(path, sizeof(path), "/proc/self/ns/%s", ns_strs[i]); + if (stat(path, &st2)) + return pr_err("Unable to stat %s", path); + if (fstat(pns, &st1)) + return pr_err("Unable to stat the parent pidns"); + if (st1.st_ino != st2.st_ino) + return pr_err("NS_GET_PARENT returned a wrong namespace"); + + if (ioctl(pns, NS_GET_PARENT) >= 0 || errno != EPERM) + return pr_err("Don't get EPERM");; + } + + kill(pid, SIGKILL); + wait(NULL); + return 0; +} -- cgit v1.2.3-59-g8ed1b From 213b067ce314f9d7e72307c7036ba3cd285b80da Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Thu, 22 Sep 2016 19:39:20 -0500 Subject: nsfs: Simplify __ns_get_path Move mntget from the very beginning of __ns_get_path to the success path of __ns_get_path, and remove the mntget calls. This removes the possibility that there will be a mntget/mntput pair of __ns_get_path has to retry, and generally simplifies the code. Signed-off-by: "Eric W. Biederman" --- fs/nsfs.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/nsfs.c b/fs/nsfs.c index fb7b397a1297..30bb10034120 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -51,7 +51,7 @@ static void nsfs_evict(struct inode *inode) static void *__ns_get_path(struct path *path, struct ns_common *ns) { - struct vfsmount *mnt = mntget(nsfs_mnt); + struct vfsmount *mnt = nsfs_mnt; struct qstr qname = { .name = "", }; struct dentry *dentry; struct inode *inode; @@ -67,7 +67,7 @@ static void *__ns_get_path(struct path *path, struct ns_common *ns) rcu_read_unlock(); ns->ops->put(ns); got_it: - path->mnt = mnt; + path->mnt = mntget(mnt); path->dentry = dentry; return NULL; slow: @@ -75,7 +75,6 @@ slow: inode = new_inode_pseudo(mnt->mnt_sb); if (!inode) { ns->ops->put(ns); - mntput(mnt); return ERR_PTR(-ENOMEM); } inode->i_ino = ns->inum; @@ -88,7 +87,6 @@ slow: dentry = d_alloc_pseudo(mnt->mnt_sb, &qname); if (!dentry) { iput(inode); - mntput(mnt); return ERR_PTR(-ENOMEM); } d_instantiate(dentry, inode); @@ -97,7 +95,6 @@ slow: if (d) { d_delete(dentry); /* make sure ->d_prune() does nothing */ dput(dentry); - mntput(mnt); cpu_relax(); return ERR_PTR(-EAGAIN); } -- cgit v1.2.3-59-g8ed1b From 2ed6afdee798658fe3c33b50c4a79d1bde45f1d8 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 23 Sep 2016 18:06:12 +0200 Subject: netns: move {inc,dec}_net_namespaces into #ifdef With the newly enforced limit on the number of namespaces, we get a build warning if CONFIG_NETNS is disabled: net/core/net_namespace.c:273:13: error: 'dec_net_namespaces' defined but not used [-Werror=unused-function] net/core/net_namespace.c:268:24: error: 'inc_net_namespaces' defined but not used [-Werror=unused-function] This moves the two added functions inside the #ifdef that guards their callers. Fixes: 703286608a22 ("netns: Add a limit on the number of net namespaces") Signed-off-by: Arnd Bergmann Signed-off-by: Eric W. Biederman --- net/core/net_namespace.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index e8be581b47b0..5e0042693c65 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -266,16 +266,6 @@ struct net *get_net_ns_by_id(struct net *net, int id) return peer; } -static struct ucounts *inc_net_namespaces(struct user_namespace *ns) -{ - return inc_ucount(ns, current_euid(), UCOUNT_NET_NAMESPACES); -} - -static void dec_net_namespaces(struct ucounts *ucounts) -{ - dec_ucount(ucounts, UCOUNT_NET_NAMESPACES); -} - /* * setup_net runs the initializers for the network namespace object. */ @@ -320,6 +310,16 @@ out_undo: #ifdef CONFIG_NET_NS +static struct ucounts *inc_net_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_NET_NAMESPACES); +} + +static void dec_net_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_NET_NAMESPACES); +} + static struct kmem_cache *net_cachep; static struct workqueue_struct *netns_wq; -- cgit v1.2.3-59-g8ed1b From d29216842a85c7970c536108e093963f02714498 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 28 Sep 2016 00:27:17 -0500 Subject: mnt: Add a per mount namespace limit on the number of mounts CAI Qian pointed out that the semantics of shared subtrees make it possible to create an exponentially increasing number of mounts in a mount namespace. mkdir /tmp/1 /tmp/2 mount --make-rshared / for i in $(seq 1 20) ; do mount --bind /tmp/1 /tmp/2 ; done Will create create 2^20 or 1048576 mounts, which is a practical problem as some people have managed to hit this by accident. As such CVE-2016-6213 was assigned. Ian Kent described the situation for autofs users as follows: > The number of mounts for direct mount maps is usually not very large because of > the way they are implemented, large direct mount maps can have performance > problems. There can be anywhere from a few (likely case a few hundred) to less > than 10000, plus mounts that have been triggered and not yet expired. > > Indirect mounts have one autofs mount at the root plus the number of mounts that > have been triggered and not yet expired. > > The number of autofs indirect map entries can range from a few to the common > case of several thousand and in rare cases up to between 30000 and 50000. I've > not heard of people with maps larger than 50000 entries. > > The larger the number of map entries the greater the possibility for a large > number of active mounts so it's not hard to expect cases of a 1000 or somewhat > more active mounts. So I am setting the default number of mounts allowed per mount namespace at 100,000. This is more than enough for any use case I know of, but small enough to quickly stop an exponential increase in mounts. Which should be perfect to catch misconfigurations and malfunctioning programs. For anyone who needs a higher limit this can be changed by writing to the new /proc/sys/fs/mount-max sysctl. Tested-by: CAI Qian Signed-off-by: "Eric W. Biederman" --- Documentation/sysctl/fs.txt | 7 +++++++ fs/mount.h | 2 ++ fs/namespace.c | 49 ++++++++++++++++++++++++++++++++++++++++++++- fs/pnode.c | 2 +- fs/pnode.h | 1 + include/linux/mount.h | 2 ++ kernel/sysctl.c | 9 +++++++++ 7 files changed, 70 insertions(+), 2 deletions(-) diff --git a/Documentation/sysctl/fs.txt b/Documentation/sysctl/fs.txt index 302b5ed616a6..35e17f748ca7 100644 --- a/Documentation/sysctl/fs.txt +++ b/Documentation/sysctl/fs.txt @@ -265,6 +265,13 @@ aio-nr can grow to. ============================================================== +mount-max: + +This denotes the maximum number of mounts that may exist +in a mount namespace. + +============================================================== + 2. /proc/sys/fs/binfmt_misc ---------------------------------------------------------- diff --git a/fs/mount.h b/fs/mount.h index e037981d8351..d2e25d7b64b3 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -14,6 +14,8 @@ struct mnt_namespace { u64 seq; /* Sequence number to prevent loops */ wait_queue_head_t poll; u64 event; + unsigned int mounts; /* # of mounts in the namespace */ + unsigned int pending_mounts; }; struct mnt_pcp { diff --git a/fs/namespace.c b/fs/namespace.c index 8a0e90eb81d3..db1b5a38864e 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -27,6 +27,9 @@ #include "pnode.h" #include "internal.h" +/* Maximum number of mounts in a mount namespace */ +unsigned int sysctl_mount_max __read_mostly = 100000; + static unsigned int m_hash_mask __read_mostly; static unsigned int m_hash_shift __read_mostly; static unsigned int mp_hash_mask __read_mostly; @@ -899,6 +902,9 @@ static void commit_tree(struct mount *mnt, struct mount *shadows) list_splice(&head, n->list.prev); + n->mounts += n->pending_mounts; + n->pending_mounts = 0; + attach_shadowed(mnt, parent, shadows); touch_mnt_namespace(n); } @@ -1419,11 +1425,16 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) propagate_umount(&tmp_list); while (!list_empty(&tmp_list)) { + struct mnt_namespace *ns; bool disconnect; p = list_first_entry(&tmp_list, struct mount, mnt_list); list_del_init(&p->mnt_expire); list_del_init(&p->mnt_list); - __touch_mnt_namespace(p->mnt_ns); + ns = p->mnt_ns; + if (ns) { + ns->mounts--; + __touch_mnt_namespace(ns); + } p->mnt_ns = NULL; if (how & UMOUNT_SYNC) p->mnt.mnt_flags |= MNT_SYNC_UMOUNT; @@ -1840,6 +1851,28 @@ static int invent_group_ids(struct mount *mnt, bool recurse) return 0; } +int count_mounts(struct mnt_namespace *ns, struct mount *mnt) +{ + unsigned int max = READ_ONCE(sysctl_mount_max); + unsigned int mounts = 0, old, pending, sum; + struct mount *p; + + for (p = mnt; p; p = next_mnt(p, mnt)) + mounts++; + + old = ns->mounts; + pending = ns->pending_mounts; + sum = old + pending; + if ((old > sum) || + (pending > sum) || + (max < sum) || + (mounts > (max - sum))) + return -ENOSPC; + + ns->pending_mounts = pending + mounts; + return 0; +} + /* * @source_mnt : mount tree to be attached * @nd : place the mount tree @source_mnt is attached @@ -1909,10 +1942,18 @@ static int attach_recursive_mnt(struct mount *source_mnt, struct path *parent_path) { HLIST_HEAD(tree_list); + struct mnt_namespace *ns = dest_mnt->mnt_ns; struct mount *child, *p; struct hlist_node *n; int err; + /* Is there space to add these mounts to the mount namespace? */ + if (!parent_path) { + err = count_mounts(ns, source_mnt); + if (err) + goto out; + } + if (IS_MNT_SHARED(dest_mnt)) { err = invent_group_ids(source_mnt, true); if (err) @@ -1949,11 +1990,13 @@ static int attach_recursive_mnt(struct mount *source_mnt, out_cleanup_ids: while (!hlist_empty(&tree_list)) { child = hlist_entry(tree_list.first, struct mount, mnt_hash); + child->mnt_parent->mnt_ns->pending_mounts = 0; umount_tree(child, UMOUNT_SYNC); } unlock_mount_hash(); cleanup_group_ids(source_mnt, NULL); out: + ns->pending_mounts = 0; return err; } @@ -2776,6 +2819,8 @@ static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *user_ns) new_ns->event = 0; new_ns->user_ns = get_user_ns(user_ns); new_ns->ucounts = ucounts; + new_ns->mounts = 0; + new_ns->pending_mounts = 0; return new_ns; } @@ -2825,6 +2870,7 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns, q = new; while (p) { q->mnt_ns = new_ns; + new_ns->mounts++; if (new_fs) { if (&p->mnt == new_fs->root.mnt) { new_fs->root.mnt = mntget(&q->mnt); @@ -2863,6 +2909,7 @@ static struct mnt_namespace *create_mnt_ns(struct vfsmount *m) struct mount *mnt = real_mount(m); mnt->mnt_ns = new_ns; new_ns->root = mnt; + new_ns->mounts++; list_add(&mnt->mnt_list, &new_ns->list); } else { mntput(m); diff --git a/fs/pnode.c b/fs/pnode.c index 99899705b105..234a9ac49958 100644 --- a/fs/pnode.c +++ b/fs/pnode.c @@ -259,7 +259,7 @@ static int propagate_one(struct mount *m) read_sequnlock_excl(&mount_lock); } hlist_add_head(&child->mnt_hash, list); - return 0; + return count_mounts(m->mnt_ns, child); } /* diff --git a/fs/pnode.h b/fs/pnode.h index 0fcdbe7ca648..550f5a8b4fcf 100644 --- a/fs/pnode.h +++ b/fs/pnode.h @@ -52,4 +52,5 @@ void mnt_set_mountpoint(struct mount *, struct mountpoint *, struct mount *copy_tree(struct mount *, struct dentry *, int); bool is_path_reachable(struct mount *, struct dentry *, const struct path *root); +int count_mounts(struct mnt_namespace *ns, struct mount *mnt); #endif /* _LINUX_PNODE_H */ diff --git a/include/linux/mount.h b/include/linux/mount.h index 54a594d49733..1172cce949a4 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -96,4 +96,6 @@ extern void mark_mounts_for_expiry(struct list_head *mounts); extern dev_t name_to_dev_t(const char *name); +extern unsigned int sysctl_mount_max; + #endif /* _LINUX_MOUNT_H */ diff --git a/kernel/sysctl.c b/kernel/sysctl.c index b43d0b27c1fe..03f18cc15697 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -65,6 +65,7 @@ #include #include #include +#include #include #include @@ -1838,6 +1839,14 @@ static struct ctl_table fs_table[] = { .mode = 0644, .proc_handler = proc_doulongvec_minmax, }, + { + .procname = "mount-max", + .data = &sysctl_mount_max, + .maxlen = sizeof(unsigned int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &one, + }, { } }; -- cgit v1.2.3-59-g8ed1b From 069d5ac9ae0d271903cc4607890616418118379a Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Fri, 30 Sep 2016 11:28:05 -0500 Subject: autofs: Fix automounts by using current_real_cred()->uid Seth Forshee reports that in 4.8-rcN some automounts are failing because the requesting the automount changed. The relevant call path is: follow_automount() ->d_automount autofs4_d_automount autofs4_mount_wait autofs4_wait In autofs4_wait wq_uid and wq_gid are set to current_uid() and current_gid respectively. With follow_automount now overriding creds uid that we export to userspace changes and that breaks existing setups. To remove the regression set wq_uid and wq_gid from current_real_cred()->uid and current_real_cred()->gid respectively. This restores the current behavior as current->real_cred is identical to current->cred except when override creds are used. Cc: stable@vger.kernel.org Fixes: aeaa4a79ff6a ("fs: Call d_automount with the filesystems creds") Reported-by: Seth Forshee Tested-by: Seth Forshee Signed-off-by: "Eric W. Biederman" --- fs/autofs4/waitq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/autofs4/waitq.c b/fs/autofs4/waitq.c index 431fd7ee3488..e44271dfceb6 100644 --- a/fs/autofs4/waitq.c +++ b/fs/autofs4/waitq.c @@ -431,8 +431,8 @@ int autofs4_wait(struct autofs_sb_info *sbi, memcpy(&wq->name, &qstr, sizeof(struct qstr)); wq->dev = autofs4_get_dev(sbi); wq->ino = autofs4_get_ino(sbi); - wq->uid = current_uid(); - wq->gid = current_gid(); + wq->uid = current_real_cred()->uid; + wq->gid = current_real_cred()->gid; wq->pid = pid; wq->tgid = tgid; wq->status = -EINTR; /* Status return if interrupted */ -- cgit v1.2.3-59-g8ed1b