[PATCH] livepatch: add (un)patch hooks
From: Joe Lawrence
Date: Wed Jul 12 2017 - 10:10:33 EST
When the livepatch core executes klp_(un)patch_object, call out to a
livepatch-module specified array of callback hooks. These hooks provide
a notification mechanism for livepatch modules when klp_objects are
(un)patching. This may be most interesting when another kernel module
is a klp_object target and the livepatch module needs to execute code
after the target is loaded, but before its module_init code is run.
The patch-hook executes right before patching objects and the
unpatch-hook executes right after unpatching objects.
Signed-off-by: Joe Lawrence <joe.lawrence@xxxxxxxxxx>
---
Documentation/livepatch/hooks.txt | 98 +++++++++++++++++++++++++
include/linux/livepatch.h | 32 ++++++++
kernel/livepatch/core.c | 5 --
kernel/livepatch/patch.c | 35 +++++++++
samples/livepatch/Makefile | 2 +
samples/livepatch/livepatch-hooks-demo.c | 122 +++++++++++++++++++++++++++++++
samples/livepatch/livepatch-hooks-mod.c | 38 ++++++++++
7 files changed, 327 insertions(+), 5 deletions(-)
create mode 100644 Documentation/livepatch/hooks.txt
create mode 100644 samples/livepatch/livepatch-hooks-demo.c
create mode 100644 samples/livepatch/livepatch-hooks-mod.c
diff --git a/Documentation/livepatch/hooks.txt b/Documentation/livepatch/hooks.txt
new file mode 100644
index 000000000000..ef18101a3b90
--- /dev/null
+++ b/Documentation/livepatch/hooks.txt
@@ -0,0 +1,98 @@
+(Un)patching Hooks
+==================
+
+Livepatching (un)patch-hooks provide a mechanism to register and execute
+a set of callback functions when the kernel's livepatching core performs
+an (un)patching operation on a given kernel object.
+
+The hooks are provided and registered by a livepatch module as part of
+klp_objects that make up its klp_patch structure. Both patch and
+unpatch-hook function signatures accept a pointer to a klp_object
+argument and return an integer status, ie:
+
+ static int patch_hook(struct klp_object *obj)
+ {
+ /* ... */
+ }
+ static int unpatch_hook(struct klp_object *obj)
+ {
+ /* ... */
+ }
+
+ static struct klp_hook patch_hooks[] = {
+ {
+ .hook = patch_hook,
+ }, { }
+ };
+ static struct klp_hook unpatch_hooks[] = {
+ {
+ .hook = unpatch_hook,
+ }, { }
+ };
+
+ static struct klp_object objs[] = {
+ {
+ /* ... */
+ .patch_hooks = patch_hooks,
+ .unpatch_hooks = unpatch_hooks,
+ }, { }
+ };
+
+ static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+ };
+
+If a hook returns non-zero status, the livepatching core will log a
+hook failure warning message.
+
+Multiple (un)patch-hooks may be registered per klp_object. Each hook
+will execute regardless of any previously executed hook's non-zero
+return status.
+
+Hooks are optional. The livepatching core will not execute any
+callbacks for an empty klp_hook.hook array or a NULL klp_hook.hook
+value.
+
+
+For module targets
+------------------
+
+In the case of kernel module objects, patch-hooks provide a livepatch
+module opportunity to defer execution until a target module is loaded.
+Similarly, unpatch-hooks only call back into a livepatch module after a
+target module has itself cleaned up. In these cases, the order of
+execution looks like:
+
+ load kernel module
+ execute all patch_hooks[] for this kernel object
+ livepatch kernel object
+ execute module_init function
+
+ ...
+
+ unload kernel module
+ execute module_exit function
+ livepatch restore kernel object
+ execute all unpatch_hooks[] for this kernel object
+
+On the other hand, if a target kernel module is already present when a
+livepatch is loading, then the corresponding patch hook(s) will execute
+as soon as the livepatching kernel core enables the livepatch.
+
+It may be useful for hooks to inspect the module state of the klp_object
+it is passed (i.e. obj->mod->state). Patch hooks can expect to see
+modules in MODULE_STATE_LIVE and MODULE_STATE_COMING states. Unpatch
+hooks can expect modules in MODULE_STATE_LIVE and MODULE_STATE_GOING
+states.
+
+
+For vmlinux target
+------------------
+
+As the kernel is always loaded, patch-hooks for vmlinux will execute as
+soon as the livepatch core enables the livepatch. Patch-hooks will also
+run if the livepatch is disabled and then re-enabled.
+
+Unpatch-hooks for vmlinux will only execute when the livepatch is
+disabled.
diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
index 194991ef9347..d95050386ac7 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -87,10 +87,23 @@ struct klp_func {
bool transition;
};
+struct klp_object;
+
+/**
+ * struct klp_hook - hook structure for live patching
+ * @hook: function to be executed on hook
+ *
+ */
+struct klp_hook {
+ int (*hook)(struct klp_object *obj);
+};
+
/**
* struct klp_object - kernel object structure for live patching
* @name: module name (or NULL for vmlinux)
* @funcs: function entries for functions to be patched in the object
+ * @patch_hooks: functions to be executed on patching
+ * @unpatch_hooks: functions to be executed on unpatching
* @kobj: kobject for sysfs resources
* @mod: kernel module associated with the patched object
* (NULL for vmlinux)
@@ -100,6 +113,8 @@ struct klp_object {
/* external */
const char *name;
struct klp_func *funcs;
+ struct klp_hook *patch_hooks;
+ struct klp_hook *unpatch_hooks;
/* internal */
struct kobject kobj;
@@ -108,6 +123,17 @@ struct klp_object {
};
/**
+ * klp_is_module() - is klp_object a module?
+ * @obj: klp_object pointer
+ *
+ * Return: true if klp_object is a loadable module
+ */
+static inline bool klp_is_module(struct klp_object *obj)
+{
+ return obj->name;
+}
+
+/**
* struct klp_patch - patch structure for live patching
* @mod: reference to the live patch module
* @objs: object entries for kernel objects to be patched
@@ -138,6 +164,12 @@ struct klp_patch {
func->old_name || func->new_func || func->old_sympos; \
func++)
+#define klp_for_each_patch_hook(obj, hook) \
+ for (hook = obj->patch_hooks; hook && hook->hook; hook++)
+
+#define klp_for_each_unpatch_hook(obj, hook) \
+ for (hook = obj->unpatch_hooks; hook && hook->hook; hook++)
+
int klp_register_patch(struct klp_patch *);
int klp_unregister_patch(struct klp_patch *);
int klp_enable_patch(struct klp_patch *);
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index b9628e43c78f..ff3685470057 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -49,11 +49,6 @@
static struct kobject *klp_root_kobj;
-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;
diff --git a/kernel/livepatch/patch.c b/kernel/livepatch/patch.c
index 52c4e907c14b..c8084a18ddb7 100644
--- a/kernel/livepatch/patch.c
+++ b/kernel/livepatch/patch.c
@@ -235,25 +235,60 @@ static int klp_patch_func(struct klp_func *func)
return ret;
}
+/**
+ * klp_run_hook - execute a given klp_hook callback
+ * @hook: callback hook
+ * @obj: kernel object that has been hooked
+ *
+ * Return: return value from hook, or 0 if none is currently associated
+ */
+static int klp_run_hook(struct klp_hook *hook, struct klp_object *obj)
+{
+ if (hook && hook->hook)
+ return (*hook->hook)(obj);
+
+ return 0;
+}
+
void klp_unpatch_object(struct klp_object *obj)
{
struct klp_func *func;
+ struct klp_hook *hook;
+ int ret;
klp_for_each_func(obj, func)
if (func->patched)
klp_unpatch_func(func);
obj->patched = false;
+
+ klp_for_each_unpatch_hook(obj, hook) {
+ ret = klp_run_hook(hook, obj);
+ if (ret) {
+ pr_warn("unpatch hook '%p' failed for object '%s'\n",
+ hook, klp_is_module(obj) ? obj->name : "vmlinux");
+ }
+ }
+
}
int klp_patch_object(struct klp_object *obj)
{
struct klp_func *func;
+ struct klp_hook *hook;
int ret;
if (WARN_ON(obj->patched))
return -EINVAL;
+ klp_for_each_patch_hook(obj, hook) {
+ ret = klp_run_hook(hook, obj);
+ if (ret) {
+ pr_warn("patch hook '%p' failed for object '%s'\n",
+ hook, klp_is_module(obj) ? obj->name : "vmlinux");
+ }
+ }
+
klp_for_each_func(obj, func) {
ret = klp_patch_func(func);
if (ret) {
diff --git a/samples/livepatch/Makefile b/samples/livepatch/Makefile
index 10319d7ea0b1..2568a56ba8f3 100644
--- a/samples/livepatch/Makefile
+++ b/samples/livepatch/Makefile
@@ -1 +1,3 @@
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-sample.o
+obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-hooks-demo.o
+obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-hooks-mod.o
diff --git a/samples/livepatch/livepatch-hooks-mod.c b/samples/livepatch/livepatch-hooks-mod.c
new file mode 100644
index 000000000000..f4ec09a5fc53
--- /dev/null
+++ b/samples/livepatch/livepatch-hooks-mod.c
@@ -0,0 +1,38 @@
+/*
+ * livepatch-hooks-mod.c - (un)patching hooks demo support module
+ *
+ * Copyright (C) 2017 Joe Lawrence <joe.lawrence@xxxxxxxxxx>
+ *
+ * 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/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+static int livepatch_hooks_mod_init(void)
+{
+ pr_info("%s\n", __func__);
+ return 0;
+}
+
+static void livepatch_hooks_mod_exit(void)
+{
+ pr_info("%s\n", __func__);
+}
+
+module_init(livepatch_hooks_mod_init);
+module_exit(livepatch_hooks_mod_exit);
+MODULE_LICENSE("GPL");
diff --git a/samples/livepatch/livepatch-hooks-demo.c b/samples/livepatch/livepatch-hooks-demo.c
new file mode 100644
index 000000000000..672a749a0549
--- /dev/null
+++ b/samples/livepatch/livepatch-hooks-demo.c
@@ -0,0 +1,122 @@
+/*
+ * livepatch-hooks-demo.c - (un)patching hooks livepatch demo
+ *
+ * Copyright (C) 2017 Joe Lawrence <joe.lawrence@xxxxxxxxxx>
+ *
+ * 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/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/livepatch.h>
+
+const char *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 hook_info(const char *hook, struct klp_object *obj)
+{
+ if (klp_is_module(obj))
+ pr_info("%s: %s\n", hook, module_state[obj->mod->state]);
+ else
+ pr_info("%s: vmlinux\n", hook);
+}
+
+/* Executed on object patching (ie, patch enablement) */
+static int patch_hook(struct klp_object *obj)
+{
+ hook_info(__func__, obj);
+ return 0;
+}
+
+/* Executed on object unpatching (ie, patch disablement) */
+static int unpatch_hook(struct klp_object *obj)
+{
+ hook_info(__func__, obj);
+ return 0;
+}
+
+static struct klp_func funcs[] = {
+ { }
+};
+
+static struct klp_hook patch_hooks[] = {
+ {
+ .hook = patch_hook,
+ }, { }
+};
+static struct klp_hook unpatch_hooks[] = {
+ {
+ .hook = unpatch_hook,
+ }, { }
+};
+
+static struct klp_object objs[] = {
+ {
+ .name = "livepatch_hooks_mod",
+ .funcs = funcs,
+ .patch_hooks = patch_hooks,
+ .unpatch_hooks = unpatch_hooks,
+ }, { }
+};
+
+static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+};
+
+static int livepatch_hooks_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_hooks_demo_exit(void)
+{
+ WARN_ON(klp_unregister_patch(&patch));
+}
+
+module_init(livepatch_hooks_demo_init);
+module_exit(livepatch_hooks_demo_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
--
1.8.3.1