kmod/core: account for failing modules in notifier

The module notifier currently only handles newly loaded modules in the
MODULE_STATE_COMING state.  If target modules need to be unloaded, the
any kpatch module that patches it must first be disabled, releasing
module references held against the target module.  When the kpatch
modules are disabled, the target module is unpatched and the kpatch
core's data structures updated accordingly.

If a loading module happens to fail its init routine (missing hardware
for example), that module will not complete loading.  The kpatch core
doesn't properly account for this "phantom" target module, so when the
kpatch patch module is removed, it spews out an ugly warning when
attempting to remove a non-existing ftrace filter on the target module.

Register an additional module notifier (first in the list) to handle the
MODULE_STATE_GOING case.  This handler needs to do the inverse of the
MODULE_STATE_COMING handler.

Fixes #699.

Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
This commit is contained in:
Joe Lawrence 2018-04-03 15:27:42 -04:00
parent 55650e16af
commit fdf36400fb
1 changed files with 68 additions and 9 deletions

View File

@ -330,7 +330,7 @@ static inline void post_patch_callback(struct kpatch_object *object)
(*object->post_patch_callback)(object);
}
static inline void pre_unpatch_callbacks(struct kpatch_object *object)
static inline void pre_unpatch_callback(struct kpatch_object *object)
{
if (kpatch_object_linked(object) &&
object->pre_unpatch_callback &&
@ -424,7 +424,7 @@ static int kpatch_remove_patch(void *data)
/* run any user-defined pre-unpatch callbacks */
list_for_each_entry(object, &kpmod->objects, list)
pre_unpatch_callbacks(object);
pre_unpatch_callback(object);
/* Check if any inconsistent NMI has happened while updating */
ret = kpatch_state_finish(KPATCH_STATE_SUCCESS);
@ -783,8 +783,10 @@ static int kpatch_unlink_object(struct kpatch_object *object)
}
}
if (object->mod)
if (object->mod) {
module_put(object->mod);
object->mod = NULL;
}
return 0;
}
@ -861,8 +863,8 @@ err_put:
return ret;
}
static int kpatch_module_notify(struct notifier_block *nb, unsigned long action,
void *data)
static int kpatch_module_notify_coming(struct notifier_block *nb,
unsigned long action, void *data)
{
struct module *mod = data;
struct kpatch_module *kpmod;
@ -917,6 +919,53 @@ out:
/* no way to stop the module load on error */
WARN(ret, "error (%d) patching newly loaded module '%s'\n", ret,
object->name);
return 0;
}
static int kpatch_module_notify_going(struct notifier_block *nb,
unsigned long action, void *data)
{
struct module *mod = data;
struct kpatch_module *kpmod;
struct kpatch_object *object;
struct kpatch_func *func;
bool found = false;
if (action != MODULE_STATE_GOING)
return 0;
down(&kpatch_mutex);
list_for_each_entry(kpmod, &kpmod_list, list) {
list_for_each_entry(object, &kpmod->objects, list) {
if (!kpatch_object_linked(object))
continue;
if (!strcmp(object->name, mod->name)) {
found = true;
goto done;
}
}
}
done:
if (!found)
goto out;
/* run user-defined pre-unpatch callback */
pre_unpatch_callback(object);
/* remove from the global func hash */
list_for_each_entry(func, &object->funcs, list)
hash_del_rcu(&func->node);
/* run user-defined pre-unpatch callback */
post_unpatch_callback(object);
kpatch_unlink_object(object);
out:
up(&kpatch_mutex);
return 0;
}
@ -1164,10 +1213,14 @@ out:
EXPORT_SYMBOL(kpatch_unregister);
static struct notifier_block kpatch_module_nb = {
.notifier_call = kpatch_module_notify,
static struct notifier_block kpatch_module_nb_coming = {
.notifier_call = kpatch_module_notify_coming,
.priority = INT_MIN, /* called last */
};
static struct notifier_block kpatch_module_nb_going = {
.notifier_call = kpatch_module_notify_going,
.priority = INT_MAX, /* called first */
};
static int kpatch_init(void)
{
@ -1189,12 +1242,17 @@ static int kpatch_init(void)
if (!kpatch_root_kobj)
return -ENOMEM;
ret = register_module_notifier(&kpatch_module_nb);
ret = register_module_notifier(&kpatch_module_nb_coming);
if (ret)
goto err_root_kobj;
ret = register_module_notifier(&kpatch_module_nb_going);
if (ret)
goto err_unregister_coming;
return 0;
err_unregister_coming:
WARN_ON(unregister_module_notifier(&kpatch_module_nb_coming));
err_root_kobj:
kobject_put(kpatch_root_kobj);
return ret;
@ -1205,7 +1263,8 @@ static void kpatch_exit(void)
rcu_barrier();
WARN_ON(kpatch_num_patched != 0);
WARN_ON(unregister_module_notifier(&kpatch_module_nb));
WARN_ON(unregister_module_notifier(&kpatch_module_nb_coming));
WARN_ON(unregister_module_notifier(&kpatch_module_nb_going));
kobject_put(kpatch_root_kobj);
}