From 653646c18333c734986db09affa7249c989375a3 Mon Sep 17 00:00:00 2001 From: Tomas Jedlicka Date: Fri, 28 Jul 2017 07:46:53 -0400 Subject: [PATCH] dtrace: modules provide called from rcu atomic section The per-module provide callback is called from within RCU read critical section. This results of running proivder code in atomic context which can cause troubles. Mainly due to sleeping allocations calls in this code path. Orabug: 26680982 Signed-off-by: Tomas Jedlicka Reviewed-by: Nick Alcock Acked-by: Somasundaram Krishnasamy --- dtrace/dtrace_dev.c | 16 +++++++++---- dtrace/dtrace_probe.c | 44 +----------------------------------- dtrace/dtrace_util.c | 40 -------------------------------- dtrace/fbt_dev.c | 5 ++-- include/dtrace/dtrace_impl.h | 3 --- include/linux/dtrace_os.h | 3 +++ kernel/dtrace/dtrace_os.c | 40 ++++++++++++++++++++++++++++++++ kernel/module.c | 3 +++ 8 files changed, 61 insertions(+), 93 deletions(-) diff --git a/dtrace/dtrace_dev.c b/dtrace/dtrace_dev.c index fb0908c14dad..6e918c2573e6 100644 --- a/dtrace/dtrace_dev.c +++ b/dtrace/dtrace_dev.c @@ -1126,8 +1126,18 @@ static struct miscdevice helper_dev = { static void module_add_pdata(void *dmy, struct module *mp) { - dtrace_module_t *pdata = kmem_cache_alloc(dtrace_pdata_cachep, - GFP_KERNEL | __GFP_ZERO); + dtrace_module_t *pdata; + + /* + * A module may be on its way out before DTrace sets up its module + * handling support. Do not try to provide anything for modules being + * removed during startup. + */ + if (mp->state == MODULE_STATE_GOING) + return; + + pdata = kmem_cache_alloc(dtrace_pdata_cachep, + GFP_KERNEL | __GFP_ZERO); pdata_init(pdata, mp); mp->pdata = pdata; @@ -1464,7 +1474,6 @@ int dtrace_dev_init(void) * a pdata object. Modules loaded after this one will get their pdata * object assigned using the module notifier hook. */ - module_add_pdata(NULL, dtrace_kmod); dtrace_for_each_module(module_add_pdata, NULL); /* @@ -1616,7 +1625,6 @@ void dtrace_dev_exit(void) * point had their pdata object cleaned up using the module notifier * hook. */ - module_del_pdata(NULL, dtrace_kmod); dtrace_for_each_module(module_del_pdata, NULL); /* diff --git a/dtrace/dtrace_probe.c b/dtrace/dtrace_probe.c index 46f7db1294d7..89a15b0c8363 100644 --- a/dtrace/dtrace_probe.c +++ b/dtrace/dtrace_probe.c @@ -163,7 +163,6 @@ void dtrace_probe_description(const dtrace_probe_t *prp, void dtrace_probe_provide(dtrace_probedesc_t *desc, dtrace_provider_t *prv) { - struct module *mod; int all = 0; if (prv == NULL) { @@ -173,48 +172,7 @@ void dtrace_probe_provide(dtrace_probedesc_t *desc, dtrace_provider_t *prv) do { prv->dtpv_pops.dtps_provide(prv->dtpv_arg, desc); - - /* - * In Linux, the kernel proper is not a module and therefore is - * not listed in the list of modules. Since the kernel proper - * can have probe points (and e.g. has a lot of SDT ones), we - * use a pseudo-kernel module to collect them so that there is - * no need for code duplication in handling such probe points. - */ - prv->dtpv_pops.dtps_provide_module(prv->dtpv_arg, dtrace_kmod); - - /* - * We need to explicitly make the call for the 'dtrace' module - * itself, because the following loop does not actually process - * the 'dtrace' module (it is used as a sentinel). - */ - prv->dtpv_pops.dtps_provide_module(prv->dtpv_arg, THIS_MODULE); - - rcu_read_lock(); - - list_for_each_entry(mod, &(THIS_MODULE->list), list) { - /* - * Skip over the modules list header, because it cannot - * validly be interpreted as a 'struct module'. It is - * a basic list_head structure. - */ -#ifdef MODULES_VADDR - if ((uintptr_t)mod < MODULES_VADDR || - (uintptr_t)mod >= MODULES_END) - continue; -#else - if ((uintptr_t)mod < VMALLOC_START || - (uintptr_t)mod >= VMALLOC_END) - continue; -#endif - - if (mod->state != MODULE_STATE_LIVE) - continue; - - prv->dtpv_pops.dtps_provide_module(prv->dtpv_arg, mod); - } - - rcu_read_unlock(); + dtrace_for_each_module(prv->dtpv_pops.dtps_provide_module, prv->dtpv_arg); } while (all && (prv = prv->dtpv_next) != NULL); } diff --git a/dtrace/dtrace_util.c b/dtrace/dtrace_util.c index a4c636a51bca..c20d7d09e3ec 100644 --- a/dtrace/dtrace_util.c +++ b/dtrace/dtrace_util.c @@ -167,43 +167,3 @@ void dtrace_cred2priv(const cred_t *cr, uint32_t *privp, kuid_t *uidp) #endif } -void dtrace_for_each_module(for_each_module_fn *fn, void *arg) -{ - struct module *mp; - - /* - * The Linux kernel does not export the modules list explicitly, nor - * is there an API to do so. However, when operating on a module - * that is in the modules list, we can traverse the entire list anyway. - * That's what we're doing here. - * - * The current module is the anchor and sentinel for the loop, so we - * need to call the worker function explicitly for that module. We - * must also identify and skip the list header because that is not a - * valid module at all. - */ - fn(arg, dtrace_kmod); - fn(arg, THIS_MODULE); - - rcu_read_lock(); - - list_for_each_entry_rcu(mp, &(THIS_MODULE->list), list) { -#ifdef MODULES_VADDR - if ((uintptr_t)mp < MODULES_VADDR || - (uintptr_t)mp >= MODULES_END) - continue; -#else - if ((uintptr_t)mp < VMALLOC_START || - (uintptr_t)mp >= VMALLOC_END) - continue; -#endif - - if (mp->state != MODULE_STATE_LIVE) - continue; - - fn(arg, mp); - } - - rcu_read_unlock(); -} -EXPORT_SYMBOL(dtrace_for_each_module); diff --git a/dtrace/fbt_dev.c b/dtrace/fbt_dev.c index 67d487e7cd6b..a160a3c4fe1b 100644 --- a/dtrace/fbt_dev.c +++ b/dtrace/fbt_dev.c @@ -110,9 +110,8 @@ void fbt_provide_module(void *arg, struct module *mp) /* * Do not try to instrument DTrace itself and its modules: - * - dtrace module - * - ctf module - * - all modules depending on dtrace + * - dtrace module + * - all modules depending on dtrace */ if (!strncmp(mp->name, "dtrace", 7)) return; diff --git a/include/dtrace/dtrace_impl.h b/include/dtrace/dtrace_impl.h index 94430006db2b..663e0a36ebd1 100644 --- a/include/dtrace/dtrace_impl.h +++ b/include/dtrace/dtrace_impl.h @@ -1122,9 +1122,6 @@ extern int dtrace_badattr(const dtrace_attribute_t *); extern int dtrace_badname(const char *); extern void dtrace_cred2priv(const cred_t *, uint32_t *, kuid_t *); -typedef void for_each_module_fn(void *, struct module *); -extern void dtrace_for_each_module(for_each_module_fn *fn, void *arg); - extern void ctf_forceload(void); #define dtrace_membar_producer() smp_wmb() diff --git a/include/linux/dtrace_os.h b/include/linux/dtrace_os.h index a9636cb711b7..86b1223a2bff 100644 --- a/include/linux/dtrace_os.h +++ b/include/linux/dtrace_os.h @@ -42,6 +42,9 @@ typedef enum dtrace_vtime_state { extern dtrace_vtime_state_t dtrace_vtime_active; +typedef void for_each_module_fn(void *, struct module *); +extern void dtrace_for_each_module(for_each_module_fn *fn, void *arg); + extern void dtrace_update_time(struct timekeeper *); extern ktime_t dtrace_get_walltime(void); diff --git a/kernel/dtrace/dtrace_os.c b/kernel/dtrace/dtrace_os.c index f32d6fd2d55d..047fd5611fc7 100644 --- a/kernel/dtrace/dtrace_os.c +++ b/kernel/dtrace/dtrace_os.c @@ -374,6 +374,46 @@ void dtrace_psinfo_free(struct task_struct *tsk) schedule_work(&psinfo_cleanup); } +/*---------------------------------------------------------------------------*\ +(* MODULE SUPPORT FUNCTIONS *) +\*---------------------------------------------------------------------------*/ +extern struct list_head *dtrace_modules; + +/* + * Iterate over all loaded kernel modules. This is requried until the linux + * kernel receives its own module iterator. + */ +void dtrace_for_each_module(for_each_module_fn func, void *arg) +{ + struct module *mp; + + if (func == NULL) + return; + + mutex_lock(&module_mutex); + + /* The dtrace fake module is not in the list. */ + func(arg, dtrace_kmod); + + list_for_each_entry(mp, dtrace_modules, list) { + +#ifdef MODULES_VADDR + if ((uintptr_t)mp < MODULES_VADDR || + (uintptr_t)mp >= MODULES_END) + continue; +#else + if ((uintptr_t)mp < VMALLOC_START || + (uintptr_t)mp >= VMALLOC_END) + continue; +#endif + + func(arg, mp); + } + + mutex_unlock(&module_mutex); +} +EXPORT_SYMBOL_GPL(dtrace_for_each_module); + /*---------------------------------------------------------------------------*\ (* TIME SUPPORT FUNCTIONS *) \*---------------------------------------------------------------------------*/ diff --git a/kernel/module.c b/kernel/module.c index d3e5e2f8ec15..7b9d678bba43 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -105,6 +105,9 @@ static LIST_HEAD(modules); #ifdef CONFIG_KGDB_KDB struct list_head *kdb_modules = &modules; /* kdb needs the list of modules */ #endif /* CONFIG_KGDB_KDB */ +#ifdef CONFIG_DTRACE +struct list_head *dtrace_modules = &modules; +#endif /* CONFIG_DTRACE */ #ifdef CONFIG_MODULE_SIG #ifdef CONFIG_MODULE_SIG_FORCE -- 2.51.0