Skip to content

Commit

Permalink
Merge branch 'for-4.15/callbacks' into for-linus
Browse files Browse the repository at this point in the history
This pulls in an infrastructure/API that allows livepatch writers to
register pre-patch and post-patch callbacks that allow for running a
glue code necessary for finalizing the patching if necessary.

Conflicts:
	kernel/livepatch/core.c
	- trivial conflict by adding a callback call into
	  module going notifier vs. moving that code block
	  to klp_cleanup_module_patches_limited()

Signed-off-by: Jiri Kosina <jkosina@suse.cz>
  • Loading branch information
Jiri Kosina committed Nov 15, 2017
2 parents cb65dc7 + 89a9a1c commit fc41efc
Show file tree
Hide file tree
Showing 10 changed files with 1,114 additions and 17 deletions.
605 changes: 605 additions & 0 deletions Documentation/livepatch/callbacks.txt

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions include/linux/livepatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,35 @@ struct klp_func {
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
* @name: module name (or NULL for vmlinux)
* @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
* @mod: kernel module associated with the patched object
* (NULL for vmlinux)
Expand All @@ -100,6 +125,7 @@ struct klp_object {
/* external */
const char *name;
struct klp_func *funcs;
struct klp_callbacks callbacks;

/* internal */
struct kobject kobj;
Expand Down
52 changes: 42 additions & 10 deletions kernel/livepatch/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}

Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -854,9 +868,15 @@ static void klp_cleanup_module_patches_limited(struct module *mod,
* 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);
Expand Down Expand Up @@ -906,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;
}
}
Expand Down
40 changes: 40 additions & 0 deletions kernel/livepatch/core.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,46 @@
#ifndef _LIVEPATCH_CORE_H
#define _LIVEPATCH_CORE_H

#include <linux/livepatch.h>

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 = 0;

if (obj->callbacks.pre_patch)
ret = (*obj->callbacks.pre_patch)(obj);

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);

obj->callbacks.post_unpatch_enabled = false;
}

#endif /* _LIVEPATCH_CORE_H */
1 change: 1 addition & 0 deletions kernel/livepatch/patch.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <linux/slab.h>
#include <linux/bug.h>
#include <linux/printk.h>
#include "core.h"
#include "patch.h"
#include "transition.h"

Expand Down
45 changes: 38 additions & 7 deletions kernel/livepatch/transition.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ static void klp_complete_transition(void)
unsigned int cpu;
bool immediate_func = false;

pr_debug("'%s': completing %s transition\n",
klp_transition_patch->mod->name,
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");

if (klp_target_state == KLP_UNPATCHED) {
/*
* All tasks have transitioned to KLP_UNPATCHED so we can now
Expand Down Expand Up @@ -109,9 +113,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 */
if (klp_target_state == KLP_PATCHED)
klp_synchronize_transition();
Expand All @@ -130,6 +131,27 @@ static void klp_complete_transition(void)
}

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);
}

pr_notice("'%s': %s complete\n", klp_transition_patch->mod->name,
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");

/*
* 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_transition_patch = NULL;
}
Expand All @@ -145,6 +167,9 @@ void klp_cancel_transition(void)
if (WARN_ON_ONCE(klp_target_state != KLP_PATCHED))
return;

pr_debug("'%s': canceling patching transition, going to unpatch\n",
klp_transition_patch->mod->name);

klp_target_state = KLP_UNPATCHED;
klp_complete_transition();
}
Expand Down Expand Up @@ -408,9 +433,6 @@ void klp_try_complete_transition(void)
}

success:
pr_notice("'%s': %s complete\n", klp_transition_patch->mod->name,
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");

/* we're done, now cleanup the data structures */
klp_complete_transition();
}
Expand All @@ -426,7 +448,8 @@ void klp_start_transition(void)

WARN_ON_ONCE(klp_target_state == KLP_UNDEFINED);

pr_notice("'%s': %s...\n", klp_transition_patch->mod->name,
pr_notice("'%s': starting %s transition\n",
klp_transition_patch->mod->name,
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");

/*
Expand Down Expand Up @@ -482,6 +505,9 @@ void klp_init_transition(struct klp_patch *patch, int state)
*/
klp_target_state = state;

pr_debug("'%s': initializing %s transition\n", patch->mod->name,
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");

/*
* If the patch can be applied or reverted immediately, skip the
* per-task transitions.
Expand Down Expand Up @@ -547,6 +573,11 @@ void klp_reverse_transition(void)
unsigned int cpu;
struct task_struct *g, *task;

pr_debug("'%s': reversing transition from %s\n",
klp_transition_patch->mod->name,
klp_target_state == KLP_PATCHED ? "patching to unpatching" :
"unpatching to patching");

klp_transition_patch->enabled = !klp_transition_patch->enabled;

klp_target_state = !klp_target_state;
Expand Down
3 changes: 3 additions & 0 deletions samples/livepatch/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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-fix1.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
72 changes: 72 additions & 0 deletions samples/livepatch/livepatch-callbacks-busymod.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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");
Loading

0 comments on commit fc41efc

Please sign in to comment.