Merge branch 'for-4.15/callbacks' into for-linus
[sfrench/cifs-2.6.git] / kernel / livepatch / core.c
index b9628e43c78f60711f8a8c21693aef5c7041f2bb..de9e45dca70f6887c50f08ad3d747540f96f50f1 100644 (file)
@@ -54,11 +54,6 @@ static bool klp_is_module(struct klp_object *obj)
        return obj->name;
 }
 
-static bool klp_is_object_loaded(struct klp_object *obj)
-{
-       return !obj->name || obj->mod;
-}
-
 /* sets obj->mod if object is not vmlinux and module is found */
 static void klp_find_object_module(struct klp_object *obj)
 {
@@ -285,6 +280,11 @@ static int klp_write_object_relocations(struct module *pmod,
 
 static int __klp_disable_patch(struct klp_patch *patch)
 {
+       struct klp_object *obj;
+
+       if (WARN_ON(!patch->enabled))
+               return -EINVAL;
+
        if (klp_transition_patch)
                return -EBUSY;
 
@@ -295,6 +295,10 @@ static int __klp_disable_patch(struct klp_patch *patch)
 
        klp_init_transition(patch, KLP_UNPATCHED);
 
+       klp_for_each_object(patch, obj)
+               if (obj->patched)
+                       klp_pre_unpatch_callback(obj);
+
        /*
         * Enforce the order of the func->transition writes in
         * klp_init_transition() and the TIF_PATCH_PENDING writes in
@@ -388,13 +392,18 @@ static int __klp_enable_patch(struct klp_patch *patch)
                if (!klp_is_object_loaded(obj))
                        continue;
 
-               ret = klp_patch_object(obj);
+               ret = klp_pre_patch_callback(obj);
                if (ret) {
-                       pr_warn("failed to enable patch '%s'\n",
-                               patch->mod->name);
+                       pr_warn("pre-patch callback failed for object '%s'\n",
+                               klp_is_module(obj) ? obj->name : "vmlinux");
+                       goto err;
+               }
 
-                       klp_cancel_transition();
-                       return ret;
+               ret = klp_patch_object(obj);
+               if (ret) {
+                       pr_warn("failed to patch object '%s'\n",
+                               klp_is_module(obj) ? obj->name : "vmlinux");
+                       goto err;
                }
        }
 
@@ -403,6 +412,11 @@ static int __klp_enable_patch(struct klp_patch *patch)
        patch->enabled = true;
 
        return 0;
+err:
+       pr_warn("failed to enable patch '%s'\n", patch->mod->name);
+
+       klp_cancel_transition();
+       return ret;
 }
 
 /**
@@ -830,6 +844,47 @@ int klp_register_patch(struct klp_patch *patch)
 }
 EXPORT_SYMBOL_GPL(klp_register_patch);
 
+/*
+ * Remove parts of patches that touch a given kernel module. The list of
+ * patches processed might be limited. When limit is NULL, all patches
+ * will be handled.
+ */
+static void klp_cleanup_module_patches_limited(struct module *mod,
+                                              struct klp_patch *limit)
+{
+       struct klp_patch *patch;
+       struct klp_object *obj;
+
+       list_for_each_entry(patch, &klp_patches, list) {
+               if (patch == limit)
+                       break;
+
+               klp_for_each_object(patch, obj) {
+                       if (!klp_is_module(obj) || strcmp(obj->name, mod->name))
+                               continue;
+
+                       /*
+                        * Only unpatch the module if the patch is enabled or
+                        * is in transition.
+                        */
+                       if (patch->enabled || patch == klp_transition_patch) {
+
+                               if (patch != klp_transition_patch)
+                                       klp_pre_unpatch_callback(obj);
+
+                               pr_notice("reverting patch '%s' on unloading module '%s'\n",
+                                         patch->mod->name, obj->mod->name);
+                               klp_unpatch_object(obj);
+
+                               klp_post_unpatch_callback(obj);
+                       }
+
+                       klp_free_object_loaded(obj);
+                       break;
+               }
+       }
+}
+
 int klp_module_coming(struct module *mod)
 {
        int ret;
@@ -871,13 +926,25 @@ int klp_module_coming(struct module *mod)
                        pr_notice("applying patch '%s' to loading module '%s'\n",
                                  patch->mod->name, obj->mod->name);
 
+                       ret = klp_pre_patch_callback(obj);
+                       if (ret) {
+                               pr_warn("pre-patch callback failed for object '%s'\n",
+                                       obj->name);
+                               goto err;
+                       }
+
                        ret = klp_patch_object(obj);
                        if (ret) {
                                pr_warn("failed to apply patch '%s' to module '%s' (%d)\n",
                                        patch->mod->name, obj->mod->name, ret);
+
+                               klp_post_unpatch_callback(obj);
                                goto err;
                        }
 
+                       if (patch != klp_transition_patch)
+                               klp_post_patch_callback(obj);
+
                        break;
                }
        }
@@ -894,7 +961,7 @@ err:
        pr_warn("patch '%s' failed for module '%s', refusing to load module '%s'\n",
                patch->mod->name, obj->mod->name, obj->mod->name);
        mod->klp_alive = false;
-       klp_free_object_loaded(obj);
+       klp_cleanup_module_patches_limited(mod, patch);
        mutex_unlock(&klp_mutex);
 
        return ret;
@@ -902,9 +969,6 @@ err:
 
 void klp_module_going(struct module *mod)
 {
-       struct klp_patch *patch;
-       struct klp_object *obj;
-
        if (WARN_ON(mod->state != MODULE_STATE_GOING &&
                    mod->state != MODULE_STATE_COMING))
                return;
@@ -917,25 +981,7 @@ void klp_module_going(struct module *mod)
         */
        mod->klp_alive = false;
 
-       list_for_each_entry(patch, &klp_patches, list) {
-               klp_for_each_object(patch, obj) {
-                       if (!klp_is_module(obj) || strcmp(obj->name, mod->name))
-                               continue;
-
-                       /*
-                        * Only unpatch the module if the patch is enabled or
-                        * is in transition.
-                        */
-                       if (patch->enabled || patch == klp_transition_patch) {
-                               pr_notice("reverting patch '%s' on unloading module '%s'\n",
-                                         patch->mod->name, obj->mod->name);
-                               klp_unpatch_object(obj);
-                       }
-
-                       klp_free_object_loaded(obj);
-                       break;
-               }
-       }
+       klp_cleanup_module_patches_limited(mod, NULL);
 
        mutex_unlock(&klp_mutex);
 }