From: Ian Kent <ik...@redhat.com> Persistent use of namespace information is needed where contained execution is needed in a namespace other than the current namespace.
Use a simple random token as a key to store namespace information in a hashed list for later usermode helper execution. Signed-off-by: Ian Kent <ik...@redhat.com> Cc: Benjamin Coddington <bcodd...@redhat.com> Cc: Al Viro <v...@zeniv.linux.org.uk> Cc: J. Bruce Fields <bfie...@fieldses.org> Cc: David Howells <dhowe...@redhat.com> Cc: Trond Myklebust <trond.mykleb...@primarydata.com> Cc: Oleg Nesterov <onest...@redhat.com> Cc: Eric W. Biederman <ebied...@xmission.com> Cc: Jeff Layton <jeff.lay...@primarydata.com> --- include/linux/kmod.h | 14 ++++ kernel/kmod.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 193 insertions(+), 6 deletions(-) diff --git a/include/linux/kmod.h b/include/linux/kmod.h index 64c81c9..77f41ce 100644 --- a/include/linux/kmod.h +++ b/include/linux/kmod.h @@ -78,6 +78,20 @@ struct subprocess_info { #endif }; +#ifndef CONFIG_NAMESPACES +static inline long umh_ns_get_token(long token) +{ + return -ENOTSUP; +} + +static inline void umh_ns_put_token(long token) +{ +} +#else +extern long umh_ns_get_token(long token); +extern void umh_ns_put_token(long token); +#endif + extern int call_usermodehelper(char *path, char **argv, char **envp, unsigned int flags); diff --git a/kernel/kmod.c b/kernel/kmod.c index d6ee21a..ddd41f1 100644 --- a/kernel/kmod.c +++ b/kernel/kmod.c @@ -41,6 +41,9 @@ #include <linux/async.h> #include <linux/proc_ns.h> #include <asm/uaccess.h> +#include <linux/hash.h> +#include <linux/list.h> +#include <linux/random.h> #include <trace/events/module.h> @@ -48,6 +51,21 @@ extern int max_threads; static struct workqueue_struct *khelper_wq; +#ifdef CONFIG_NAMESPACES +#define UMH_HASH_SHIFT 6 +#define UMH_HASH_SIZE 1 << UMH_HASH_SHIFT + +struct umh_ns_entry { + long token; + unsigned int count; + struct umh_ns_info nsinfo; + struct hlist_node umh_ns_hlist; +}; + +static DEFINE_SPINLOCK(umh_ns_hash_lock); +static struct hlist_head umh_ns_hash[UMH_HASH_SIZE]; +#endif + #define CAP_BSET (void *)1 #define CAP_PI (void *)2 @@ -577,10 +595,13 @@ static int umh_get_nsproxy(struct subprocess_info *sub_info) static void umh_put_nsproxy(struct subprocess_info *sub_info) { } + +static void umh_ns_hash_init(void) +{ +} #else -static int umh_get_nsproxy(struct subprocess_info *sub_info) +static int _umh_get_nsproxy(struct umh_ns_info *nsinfo) { - struct umh_ns_info *nsinfo = &sub_info->nsinfo; struct task_struct *tsk; struct user_namespace *user_ns; struct nsproxy *new; @@ -614,14 +635,165 @@ out: return err; } +static int umh_get_nsproxy(struct subprocess_info *sub_info) +{ + return _umh_get_nsproxy(&sub_info->nsinfo); +} + +static void _umh_put_nsproxy(struct umh_ns_info *nsinfo) +{ + if (nsinfo->nsproxy) { + put_nsproxy(nsinfo->nsproxy); + put_user_ns(nsinfo->user_ns); + } +} + static void umh_put_nsproxy(struct subprocess_info *sub_info) { - if (sub_info->nsinfo.nsproxy) { - put_nsproxy(sub_info->nsinfo.nsproxy); - put_user_ns(sub_info->nsinfo.user_ns); + return _umh_put_nsproxy(&sub_info->nsinfo); +} + +static void umh_ns_hash_init(void) +{ + int i; + + for (i = 0; i < UMH_HASH_SIZE; i++) + INIT_HLIST_HEAD(&umh_ns_hash[i]); +} + +static struct umh_ns_entry *__umh_ns_find_entry(long token) +{ + struct umh_ns_entry *this, *entry; + struct hlist_head *bucket; + unsigned int hash; + + hash = hash_64((unsigned long) token, UMH_HASH_SHIFT); + bucket = &umh_ns_hash[hash]; + + entry = ERR_PTR(-ENOENT); + if (hlist_empty(bucket)) + goto out; + + hlist_for_each_entry(this, bucket, umh_ns_hlist) { + if (this->token == token) { + entry = this; + break; + } } +out: + return entry; } -#endif + +static struct umh_ns_entry *umh_ns_find_entry(long token, unsigned int nowait) +{ + struct umh_ns_entry *entry; + unsigned long flags; + + if (nowait) + spin_lock_irqsave(&umh_ns_hash_lock, flags); + else + spin_lock(&umh_ns_hash_lock); + entry = __umh_ns_find_entry(token); + if (nowait) + spin_unlock_irqrestore(&umh_ns_hash_lock, flags); + else + spin_unlock(&umh_ns_hash_lock); + + return entry; +} + +/** + * umh_ns_get_token - allocate and store namespace information of the + * init process of the caller + * @token: token of stored namspace information or zero for a new + * token. + * + * Returns a token used to locate the namespace information for calls to + * call_usermode_helper_ns() calls. On failure returns a negative errno. + */ +long umh_ns_get_token(long token) +{ + struct umh_ns_entry *entry; + struct hlist_head *bucket; + unsigned int hash; + unsigned int new_token; + int err; + + if (token) { + spin_lock(&umh_ns_hash_lock); + entry = __umh_ns_find_entry(token); + if (entry) { + entry->count++; + spin_unlock(&umh_ns_hash_lock); + return token; + } + spin_unlock(&umh_ns_hash_lock); + } + + entry = kzalloc(sizeof(struct umh_ns_entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + err = _umh_get_nsproxy(&entry->nsinfo); + if (err) { + kfree(entry); + return err; + } + + do { + new_token = get_random_int(); + if (!token) + continue; + spin_lock(&umh_ns_hash_lock); + entry = __umh_ns_find_entry(new_token); + if (likely(IS_ERR(entry))) { + hash = hash_32(new_token, UMH_HASH_SHIFT); + bucket = &umh_ns_hash[hash]; + hlist_add_head(&entry->umh_ns_hlist, bucket); + entry->token = (long) new_token; + spin_unlock(&umh_ns_hash_lock); + break; + } + spin_unlock(&umh_ns_hash_lock); + } while (1); + + return (long) new_token; +} +EXPORT_SYMBOL(umh_ns_get_token); + +/** + * umh_ns_put_token - remove and free a stored namespace corresponding to + * passed token + * @token: token of the stored namspace information. + * + * Returns 0 if the token is not longer in use or the token if it is in + * use. + */ +void umh_ns_put_token(long token) +{ + struct umh_ns_entry *entry; + + if (!token) + return; + + spin_lock(&umh_ns_hash_lock); + entry = __umh_ns_find_entry(token); + if (unlikely(IS_ERR(entry))) + spin_unlock(&umh_ns_hash_lock); + else { + if (--entry->count) + spin_unlock(&umh_ns_hash_lock); + else { + hlist_del(&entry->umh_ns_hlist); + spin_unlock(&umh_ns_hash_lock); + _umh_put_nsproxy(&entry->nsinfo); + kfree(entry); + } + } + return; +} +EXPORT_SYMBOL(umh_ns_put_token); +#endif /* CONFIG_NAMESPACES */ /** * call_usermodehelper_setup - prepare to call a usermode helper @@ -849,4 +1021,5 @@ void __init usermodehelper_init(void) { khelper_wq = create_singlethread_workqueue("khelper"); BUG_ON(!khelper_wq); + umh_ns_hash_init(); } -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/