Commit 93862e38 authored by Joe Lawrence's avatar Joe Lawrence Committed by Jiri Kosina

livepatch: add (un)patch callbacks

Provide livepatch modules a klp_object (un)patching notification
mechanism.  Pre and post-(un)patch callbacks allow livepatch modules to
setup or synchronize changes that would be difficult to support in only
patched-or-unpatched code contexts.

Callbacks can be registered for target module or vmlinux klp_objects,
but each implementation is klp_object specific.

  - Pre-(un)patch callbacks run before any (un)patching transition
    starts.

  - Post-(un)patch callbacks run once an object has been (un)patched and
    the klp_patch fully transitioned to its target state.

Example use cases include modification of global data and registration
of newly available services/handlers.

See Documentation/livepatch/callbacks.txt for details and
samples/livepatch/ for examples.
Signed-off-by: default avatarJoe Lawrence <joe.lawrence@redhat.com>
Acked-by: default avatarJosh Poimboeuf <jpoimboe@redhat.com>
Acked-by: default avatarMiroslav Benes <mbenes@suse.cz>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 19205da6
This diff is collapsed.
...@@ -87,10 +87,35 @@ struct klp_func { ...@@ -87,10 +87,35 @@ struct klp_func {
bool transition; bool transition;
}; };
struct klp_object;
/**
* struct klp_callbacks - pre/post live-(un)patch callback structure
* @pre_patch: executed before code patching
* @post_patch: executed after code patching
* @pre_unpatch: executed before code unpatching
* @post_unpatch: executed after code unpatching
* @post_unpatch_enabled: flag indicating if post-unpatch callback
* should run
*
* All callbacks are optional. Only the pre-patch callback, if provided,
* will be unconditionally executed. If the parent klp_object fails to
* patch for any reason, including a non-zero error status returned from
* the pre-patch callback, no further callbacks will be executed.
*/
struct klp_callbacks {
int (*pre_patch)(struct klp_object *obj);
void (*post_patch)(struct klp_object *obj);
void (*pre_unpatch)(struct klp_object *obj);
void (*post_unpatch)(struct klp_object *obj);
bool post_unpatch_enabled;
};
/** /**
* struct klp_object - kernel object structure for live patching * struct klp_object - kernel object structure for live patching
* @name: module name (or NULL for vmlinux) * @name: module name (or NULL for vmlinux)
* @funcs: function entries for functions to be patched in the object * @funcs: function entries for functions to be patched in the object
* @callbacks: functions to be executed pre/post (un)patching
* @kobj: kobject for sysfs resources * @kobj: kobject for sysfs resources
* @mod: kernel module associated with the patched object * @mod: kernel module associated with the patched object
* (NULL for vmlinux) * (NULL for vmlinux)
...@@ -100,6 +125,7 @@ struct klp_object { ...@@ -100,6 +125,7 @@ struct klp_object {
/* external */ /* external */
const char *name; const char *name;
struct klp_func *funcs; struct klp_func *funcs;
struct klp_callbacks callbacks;
/* internal */ /* internal */
struct kobject kobj; struct kobject kobj;
......
...@@ -54,11 +54,6 @@ static bool klp_is_module(struct klp_object *obj) ...@@ -54,11 +54,6 @@ static bool klp_is_module(struct klp_object *obj)
return obj->name; 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 */ /* sets obj->mod if object is not vmlinux and module is found */
static void klp_find_object_module(struct klp_object *obj) static void klp_find_object_module(struct klp_object *obj)
{ {
...@@ -285,6 +280,8 @@ static int klp_write_object_relocations(struct module *pmod, ...@@ -285,6 +280,8 @@ static int klp_write_object_relocations(struct module *pmod,
static int __klp_disable_patch(struct klp_patch *patch) static int __klp_disable_patch(struct klp_patch *patch)
{ {
struct klp_object *obj;
if (klp_transition_patch) if (klp_transition_patch)
return -EBUSY; return -EBUSY;
...@@ -295,6 +292,10 @@ static int __klp_disable_patch(struct klp_patch *patch) ...@@ -295,6 +292,10 @@ static int __klp_disable_patch(struct klp_patch *patch)
klp_init_transition(patch, KLP_UNPATCHED); klp_init_transition(patch, KLP_UNPATCHED);
klp_for_each_object(patch, obj)
if (patch->enabled && obj->patched)
klp_pre_unpatch_callback(obj);
/* /*
* Enforce the order of the func->transition writes in * Enforce the order of the func->transition writes in
* klp_init_transition() and the TIF_PATCH_PENDING writes in * klp_init_transition() and the TIF_PATCH_PENDING writes in
...@@ -388,13 +389,18 @@ static int __klp_enable_patch(struct klp_patch *patch) ...@@ -388,13 +389,18 @@ static int __klp_enable_patch(struct klp_patch *patch)
if (!klp_is_object_loaded(obj)) if (!klp_is_object_loaded(obj))
continue; continue;
ret = klp_patch_object(obj); ret = klp_pre_patch_callback(obj);
if (ret) { if (ret) {
pr_warn("failed to enable patch '%s'\n", pr_warn("pre-patch callback failed for object '%s'\n",
patch->mod->name); klp_is_module(obj) ? obj->name : "vmlinux");
goto err;
}
klp_cancel_transition(); ret = klp_patch_object(obj);
return ret; if (ret) {
pr_warn("failed to patch object '%s'\n",
klp_is_module(obj) ? obj->name : "vmlinux");
goto err;
} }
} }
...@@ -403,6 +409,11 @@ static int __klp_enable_patch(struct klp_patch *patch) ...@@ -403,6 +409,11 @@ static int __klp_enable_patch(struct klp_patch *patch)
patch->enabled = true; patch->enabled = true;
return 0; return 0;
err:
pr_warn("failed to enable patch '%s'\n", patch->mod->name);
klp_cancel_transition();
return ret;
} }
/** /**
...@@ -871,13 +882,27 @@ int klp_module_coming(struct module *mod) ...@@ -871,13 +882,27 @@ int klp_module_coming(struct module *mod)
pr_notice("applying patch '%s' to loading module '%s'\n", pr_notice("applying patch '%s' to loading module '%s'\n",
patch->mod->name, obj->mod->name); 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); ret = klp_patch_object(obj);
if (ret) { if (ret) {
pr_warn("failed to apply patch '%s' to module '%s' (%d)\n", pr_warn("failed to apply patch '%s' to module '%s' (%d)\n",
patch->mod->name, obj->mod->name, ret); patch->mod->name, obj->mod->name, ret);
if (patch != klp_transition_patch)
klp_post_unpatch_callback(obj);
goto err; goto err;
} }
if (patch != klp_transition_patch)
klp_post_patch_callback(obj);
break; break;
} }
} }
...@@ -927,9 +952,15 @@ void klp_module_going(struct module *mod) ...@@ -927,9 +952,15 @@ void klp_module_going(struct module *mod)
* is in transition. * is in transition.
*/ */
if (patch->enabled || patch == klp_transition_patch) { 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", pr_notice("reverting patch '%s' on unloading module '%s'\n",
patch->mod->name, obj->mod->name); patch->mod->name, obj->mod->name);
klp_unpatch_object(obj); klp_unpatch_object(obj);
klp_post_unpatch_callback(obj);
} }
klp_free_object_loaded(obj); klp_free_object_loaded(obj);
......
#ifndef _LIVEPATCH_CORE_H #ifndef _LIVEPATCH_CORE_H
#define _LIVEPATCH_CORE_H #define _LIVEPATCH_CORE_H
#include <linux/livepatch.h>
extern struct mutex klp_mutex; extern struct mutex klp_mutex;
static inline bool klp_is_object_loaded(struct klp_object *obj)
{
return !obj->name || obj->mod;
}
static inline int klp_pre_patch_callback(struct klp_object *obj)
{
int ret;
ret = (obj->callbacks.pre_patch) ?
(*obj->callbacks.pre_patch)(obj) : 0;
obj->callbacks.post_unpatch_enabled = !ret;
return ret;
}
static inline void klp_post_patch_callback(struct klp_object *obj)
{
if (obj->callbacks.post_patch)
(*obj->callbacks.post_patch)(obj);
}
static inline void klp_pre_unpatch_callback(struct klp_object *obj)
{
if (obj->callbacks.pre_unpatch)
(*obj->callbacks.pre_unpatch)(obj);
}
static inline void klp_post_unpatch_callback(struct klp_object *obj)
{
if (obj->callbacks.post_unpatch_enabled &&
obj->callbacks.post_unpatch)
(*obj->callbacks.post_unpatch)(obj);
}
#endif /* _LIVEPATCH_CORE_H */ #endif /* _LIVEPATCH_CORE_H */
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/bug.h> #include <linux/bug.h>
#include <linux/printk.h> #include <linux/printk.h>
#include "core.h"
#include "patch.h" #include "patch.h"
#include "transition.h" #include "transition.h"
......
...@@ -109,9 +109,6 @@ static void klp_complete_transition(void) ...@@ -109,9 +109,6 @@ static void klp_complete_transition(void)
} }
} }
if (klp_target_state == KLP_UNPATCHED && !immediate_func)
module_put(klp_transition_patch->mod);
/* Prevent klp_ftrace_handler() from seeing KLP_UNDEFINED state */ /* Prevent klp_ftrace_handler() from seeing KLP_UNDEFINED state */
if (klp_target_state == KLP_PATCHED) if (klp_target_state == KLP_PATCHED)
klp_synchronize_transition(); klp_synchronize_transition();
...@@ -130,6 +127,24 @@ static void klp_complete_transition(void) ...@@ -130,6 +127,24 @@ static void klp_complete_transition(void)
} }
done: done:
klp_for_each_object(klp_transition_patch, obj) {
if (!klp_is_object_loaded(obj))
continue;
if (klp_target_state == KLP_PATCHED)
klp_post_patch_callback(obj);
else if (klp_target_state == KLP_UNPATCHED)
klp_post_unpatch_callback(obj);
}
/*
* See complementary comment in __klp_enable_patch() for why we
* keep the module reference for immediate patches.
*/
if (!klp_transition_patch->immediate && !immediate_func &&
klp_target_state == KLP_UNPATCHED) {
module_put(klp_transition_patch->mod);
}
klp_target_state = KLP_UNDEFINED; klp_target_state = KLP_UNDEFINED;
klp_transition_patch = NULL; klp_transition_patch = NULL;
} }
......
...@@ -2,3 +2,6 @@ obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-sample.o ...@@ -2,3 +2,6 @@ obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-sample.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-shadow-mod.o obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-shadow-mod.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-shadow-fix1.o obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-shadow-fix1.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-shadow-fix2.o obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-shadow-fix2.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-demo.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-mod.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-busymod.o
/*
* Copyright (C) 2017 Joe Lawrence <joe.lawrence@redhat.com>
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* livepatch-callbacks-busymod.c - (un)patching callbacks demo support module
*
*
* Purpose
* -------
*
* Simple module to demonstrate livepatch (un)patching callbacks.
*
*
* Usage
* -----
*
* This module is not intended to be standalone. See the "Usage"
* section of livepatch-callbacks-mod.c.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
static int sleep_secs;
module_param(sleep_secs, int, 0644);
MODULE_PARM_DESC(sleep_secs, "sleep_secs (default=0)");
static void busymod_work_func(struct work_struct *work);
static DECLARE_DELAYED_WORK(work, busymod_work_func);
static void busymod_work_func(struct work_struct *work)
{
pr_info("%s, sleeping %d seconds ...\n", __func__, sleep_secs);
msleep(sleep_secs * 1000);
pr_info("%s exit\n", __func__);
}
static int livepatch_callbacks_mod_init(void)
{
pr_info("%s\n", __func__);
schedule_delayed_work(&work,
msecs_to_jiffies(1000 * 0));
return 0;
}
static void livepatch_callbacks_mod_exit(void)
{
cancel_delayed_work_sync(&work);
pr_info("%s\n", __func__);
}
module_init(livepatch_callbacks_mod_init);
module_exit(livepatch_callbacks_mod_exit);
MODULE_LICENSE("GPL");
/*
* Copyright (C) 2017 Joe Lawrence <joe.lawrence@redhat.com>
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* livepatch-callbacks-demo.c - (un)patching callbacks livepatch demo
*
*
* Purpose
* -------
*
* Demonstration of registering livepatch (un)patching callbacks.
*
*
* Usage
* -----
*
* Step 1 - load the simple module
*
* insmod samples/livepatch/livepatch-callbacks-mod.ko
*
*
* Step 2 - load the demonstration livepatch (with callbacks)
*
* insmod samples/livepatch/livepatch-callbacks-demo.ko
*
*
* Step 3 - cleanup
*
* echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
* rmmod livepatch_callbacks_demo
* rmmod livepatch_callbacks_mod
*
* Watch dmesg output to see livepatch enablement, callback execution
* and patching operations for both vmlinux and module targets.
*
* NOTE: swap the insmod order of livepatch-callbacks-mod.ko and
* livepatch-callbacks-demo.ko to observe what happens when a
* target module is loaded after a livepatch with callbacks.
*
* NOTE: 'pre_patch_ret' is a module parameter that sets the pre-patch
* callback return status. Try setting up a non-zero status
* such as -19 (-ENODEV):
*
* # Load demo livepatch, vmlinux is patched
* insmod samples/livepatch/livepatch-callbacks-demo.ko
*
* # Setup next pre-patch callback to return -ENODEV
* echo -19 > /sys/module/livepatch_callbacks_demo/parameters/pre_patch_ret
*
* # Module loader refuses to load the target module
* insmod samples/livepatch/livepatch-callbacks-mod.ko
* insmod: ERROR: could not insert module samples/livepatch/livepatch-callbacks-mod.ko: No such device
*
* NOTE: There is a second target module,
* livepatch-callbacks-busymod.ko, available for experimenting
* with livepatch (un)patch callbacks. This module contains
* a 'sleep_secs' parameter that parks the module on one of the
* functions that the livepatch demo module wants to patch.
* Modifying this value and tweaking the order of module loads can
* effectively demonstrate stalled patch transitions:
*
* # Load a target module, let it park on 'busymod_work_func' for
* # thirty seconds
* insmod samples/livepatch/livepatch-callbacks-busymod.ko sleep_secs=30
*
* # Meanwhile load the livepatch
* insmod samples/livepatch/livepatch-callbacks-demo.ko
*
* # ... then load and unload another target module while the
* # transition is in progress
* insmod samples/livepatch/livepatch-callbacks-mod.ko
* rmmod samples/livepatch/livepatch-callbacks-mod.ko
*
* # Finally cleanup
* echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
* rmmod samples/livepatch/livepatch-callbacks-demo.ko
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/livepatch.h>
static int pre_patch_ret;
module_param(pre_patch_ret, int, 0644);
MODULE_PARM_DESC(pre_patch_ret, "pre_patch_ret (default=0)");
static const char *const module_state[] = {
[MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
[MODULE_STATE_COMING] = "[MODULE_STATE_COMING] Full formed, running module_init",
[MODULE_STATE_GOING] = "[MODULE_STATE_GOING] Going away",
[MODULE_STATE_UNFORMED] = "[MODULE_STATE_UNFORMED] Still setting it up",
};
static void callback_info(const char *callback, struct klp_object *obj)
{
if (obj->mod)
pr_info("%s: %s -> %s\n", callback, obj->mod->name,
module_state[obj->mod->state]);
else
pr_info("%s: vmlinux\n", callback);
}
/* Executed on object patching (ie, patch enablement) */
static int pre_patch_callback(struct klp_object *obj)
{
callback_info(__func__, obj);
return pre_patch_ret;
}
/* Executed on object unpatching (ie, patch disablement) */
static void post_patch_callback(struct klp_object *obj)
{
callback_info(__func__, obj);
}
/* Executed on object unpatching (ie, patch disablement) */
static void pre_unpatch_callback(struct klp_object *obj)
{
callback_info(__func__, obj);
}
/* Executed on object unpatching (ie, patch disablement) */
static void post_unpatch_callback(struct klp_object *obj)
{
callback_info(__func__, obj);
}
static void patched_work_func(struct work_struct *work)
{
pr_info("%s\n", __func__);
}
static struct klp_func no_funcs[] = {
{ }
};
static struct klp_func busymod_funcs[] = {
{
.old_name = "busymod_work_func",
.new_func = patched_work_func,
}, { }
};
static struct klp_object objs[] = {
{
.name = NULL, /* vmlinux */
.funcs = no_funcs,
.callbacks = {
.pre_patch = pre_patch_callback,
.post_patch = post_patch_callback,
.pre_unpatch = pre_unpatch_callback,
.post_unpatch = post_unpatch_callback,
},
}, {
.name = "livepatch_callbacks_mod",
.funcs = no_funcs,
.callbacks = {
.pre_patch = pre_patch_callback,
.post_patch = post_patch_callback,
.pre_unpatch = pre_unpatch_callback,
.post_unpatch = post_unpatch_callback,
},
}, {
.name = "livepatch_callbacks_busymod",
.funcs = busymod_funcs,
.callbacks = {
.pre_patch = pre_patch_callback,
.post_patch = post_patch_callback,
.pre_unpatch = pre_unpatch_callback,
.post_unpatch = post_unpatch_callback,
},
}, { }
};
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
};
static int livepatch_callbacks_demo_init(void)
{
int ret;
if (!klp_have_reliable_stack() && !patch.immediate) {
/*
* WARNING: Be very careful when using 'patch.immediate' in
* your patches. It's ok to use it for simple patches like
* this, but for more complex patches which change function
* semantics, locking semantics, or data structures, it may not
* be safe. Use of this option will also prevent removal of
* the patch.
*
* See Documentation/livepatch/livepatch.txt for more details.
*/
patch.immediate = true;
pr_notice("The consistency model isn't supported for your architecture. Bypassing safety mechanisms and applying the patch immediately.\n");
}
ret = klp_register_patch(&patch);
if (ret)
return ret;
ret = klp_enable_patch(&patch);
if (ret) {
WARN_ON(klp_unregister_patch(&patch));
return ret;
}
return 0;
}
static void livepatch_callbacks_demo_exit(void)
{
WARN_ON(klp_unregister_patch(&patch));
}
module_init(livepatch_callbacks_demo_init);
module_exit(livepatch_callbacks_demo_exit);
MODULE_LICENSE("GPL");
MODULE_INFO(livepatch, "Y");
/*
* Copyright (C) 2017 Joe Lawrence <joe.lawrence@redhat.com>
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* livepatch-callbacks-mod.c - (un)patching callbacks demo support module
*
*
* Purpose
* -------
*
* Simple module to demonstrate livepatch (un)patching callbacks.
*
*
* Usage
* -----
*
* This module is not intended to be standalone. See the "Usage"
* section of livepatch-callbacks-demo.c.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
static int livepatch_callbacks_mod_init(void)
{
pr_info("%s\n", __func__);
return 0;
}
static void livepatch_callbacks_mod_exit(void)
{
pr_info("%s\n", __func__);
}
module_init(livepatch_callbacks_mod_init);
module_exit(livepatch_callbacks_mod_exit);
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment