[RFC PATCH 2/9] livepatch: separate enabled and patched states

Josh Poimboeuf
2015-02-09T17:33:11Z

Once we have a consistency model, patches and their objects will be
enabled and disabled at different times. For example, when a patch is
disabled, its loaded objects' funcs can remain registered with ftrace
indefinitely until the unpatching operation is complete and they're no
longer in use.

It's less confusing if we give them different names: patches can be
enabled or disabled; objects (and their funcs) can be patched or
unpatched:

- Enabled means that a patch is logically enabled (but not necessarily
fully applied).

- Patched means that an object's funcs are registered with ftrace and
added to the klp_ops func stack.

Also, since these states are binary, represent them with boolean-type
variables instead of enums.

Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
---
include/linux/livepatch.h | 15 ++++-----
kernel/livepatch/core.c | 79 +++++++++++++++++++++++------------------------
2 files changed, 45 insertions(+), 49 deletions(-)

diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
index 95023fd..22a67d1 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -28,11 +28,6 @@

#include <asm/livepatch.h>

-enum klp_state {
- KLP_DISABLED,
- KLP_ENABLED
-};
-
/**
* struct klp_func - function structure for live patching
* @old_name: name of the function to be patched
@@ -42,6 +37,7 @@ enum klp_state {
* @kobj: kobject for sysfs resources
* @state: tracks function-level patch application state
* @stack_node: list node for klp_ops func_stack list
+ * @patched: the func has been added to the klp_ops list
*/
struct klp_func {
/* external */
@@ -59,8 +55,8 @@ struct klp_func {

/* internal */
struct kobject kobj;
- enum klp_state state;
struct list_head stack_node;
+ int patched;
};

/**
@@ -90,7 +86,7 @@ struct klp_reloc {
* @kobj: kobject for sysfs resources
* @mod: kernel module associated with the patched object
* (NULL for vmlinux)
- * @state: tracks object-level patch application state
+ * @patched: the object's funcs have been add to the klp_ops list
*/
struct klp_object {
/* external */
@@ -101,7 +97,7 @@ struct klp_object {
/* internal */
struct kobject *kobj;
struct module *mod;
- enum klp_state state;
+ int patched;
};

/**
@@ -111,6 +107,7 @@ struct klp_object {
* @list: list node for global list of registered patches
* @kobj: kobject for sysfs resources
* @state: tracks patch-level application state
+ * @enabled: the patch is enabled (but operation may be incomplete)
*/
struct klp_patch {
/* external */
@@ -120,7 +117,7 @@ struct klp_patch {
/* internal */
struct list_head list;
struct kobject kobj;
- enum klp_state state;
+ int enabled;
};

extern int klp_register_patch(struct klp_patch *);
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 081df77..73f9ba4 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -322,11 +322,11 @@ static void notrace klp_ftrace_handler(unsigned long ip,
klp_arch_set_pc(regs, (unsigned long)func->new_func);
}

-static void klp_disable_func(struct klp_func *func)
+static void klp_unpatch_func(struct klp_func *func)
{
struct klp_ops *ops;

- WARN_ON(func->state != KLP_ENABLED);
+ WARN_ON(!func->patched);
WARN_ON(!func->old_addr);

ops = klp_find_ops(func->old_addr);
@@ -344,10 +344,10 @@ static void klp_disable_func(struct klp_func *func)
list_del_rcu(&func->stack_node);
}

- func->state = KLP_DISABLED;
+ func->patched = 0;
}

-static int klp_enable_func(struct klp_func *func)
+static int klp_patch_func(struct klp_func *func)
{
struct klp_ops *ops;
int ret;
@@ -355,7 +355,7 @@ static int klp_enable_func(struct klp_func *func)
if (WARN_ON(!func->old_addr))
return -EINVAL;

- if (WARN_ON(func->state != KLP_DISABLED))
+ if (WARN_ON(func->patched))
return -EINVAL;

ops = klp_find_ops(func->old_addr);
@@ -394,7 +394,7 @@ static int klp_enable_func(struct klp_func *func)
list_add_rcu(&func->stack_node, &ops->func_stack);
}

- func->state = KLP_ENABLED;
+ func->patched = 1;

return 0;

@@ -405,36 +405,36 @@ err:
return ret;
}

-static void klp_disable_object(struct klp_object *obj)
+static void klp_unpatch_object(struct klp_object *obj)
{
struct klp_func *func;

for (func = obj->funcs; func->old_name; func++)
- if (func->state == KLP_ENABLED)
- klp_disable_func(func);
+ if (func->patched)
+ klp_unpatch_func(func);

- obj->state = KLP_DISABLED;
+ obj->patched = 0;
}

-static int klp_enable_object(struct klp_object *obj)
+static int klp_patch_object(struct klp_object *obj)
{
struct klp_func *func;
int ret;

- if (WARN_ON(obj->state != KLP_DISABLED))
+ if (WARN_ON(obj->patched))
return -EINVAL;

if (WARN_ON(!klp_is_object_loaded(obj)))
return -EINVAL;

for (func = obj->funcs; func->old_name; func++) {
- ret = klp_enable_func(func);
+ ret = klp_patch_func(func);
if (ret) {
- klp_disable_object(obj);
+ klp_unpatch_object(obj);
return ret;
}
}
- obj->state = KLP_ENABLED;
+ obj->patched = 1;

return 0;
}
@@ -445,17 +445,16 @@ static int __klp_disable_patch(struct klp_patch *patch)

/* enforce stacking: only the last enabled patch can be disabled */
if (!list_is_last(&patch->list, &klp_patches) &&
- list_next_entry(patch, list)->state == KLP_ENABLED)
+ list_next_entry(patch, list)->enabled)
return -EBUSY;

pr_notice("disabling patch '%s'\n", patch->mod->name);

- for (obj = patch->objs; obj->funcs; obj++) {
- if (obj->state == KLP_ENABLED)
- klp_disable_object(obj);
- }
+ for (obj = patch->objs; obj->funcs; obj++)
+ if (obj->patched)
+ klp_unpatch_object(obj);

- patch->state = KLP_DISABLED;
+ patch->enabled = 0;

return 0;
}
@@ -479,7 +478,7 @@ int klp_disable_patch(struct klp_patch *patch)
goto err;
}

- if (patch->state == KLP_DISABLED) {
+ if (!patch->enabled) {
ret = -EINVAL;
goto err;
}
@@ -497,12 +496,12 @@ static int __klp_enable_patch(struct klp_patch *patch)
struct klp_object *obj;
int ret;

- if (WARN_ON(patch->state != KLP_DISABLED))
+ if (WARN_ON(patch->enabled))
return -EINVAL;

/* enforce stacking: only the first disabled patch can be enabled */
if (patch->list.prev != &klp_patches &&
- list_prev_entry(patch, list)->state == KLP_DISABLED)
+ !list_prev_entry(patch, list)->enabled)
return -EBUSY;

pr_notice_once("tainting kernel with TAINT_LIVEPATCH\n");
@@ -516,12 +515,12 @@ static int __klp_enable_patch(struct klp_patch *patch)
if (!klp_is_object_loaded(obj))
continue;

- ret = klp_enable_object(obj);
+ ret = klp_patch_object(obj);
if (ret)
goto unregister;
}

- patch->state = KLP_ENABLED;
+ patch->enabled = 1;

return 0;

@@ -579,20 +578,20 @@ static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr,
if (ret)
return -EINVAL;

- if (val != KLP_DISABLED && val != KLP_ENABLED)
+ if (val > 1)
return -EINVAL;

patch = container_of(kobj, struct klp_patch, kobj);

mutex_lock(&klp_mutex);

- if (val == patch->state) {
+ if (patch->enabled == val) {
/* already in requested state */
ret = -EINVAL;
goto err;
}

- if (val == KLP_ENABLED) {
+ if (val) {
ret = __klp_enable_patch(patch);
if (ret)
goto err;
@@ -617,7 +616,7 @@ static ssize_t enabled_show(struct kobject *kobj,
struct klp_patch *patch;

patch = container_of(kobj, struct klp_patch, kobj);
- return snprintf(buf, PAGE_SIZE-1, "%d\n", patch->state);
+ return snprintf(buf, PAGE_SIZE-1, "%d\n", patch->enabled);
}

static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled);
@@ -699,7 +698,7 @@ static void klp_free_patch(struct klp_patch *patch)
static int klp_init_func(struct klp_object *obj, struct klp_func *func)
{
INIT_LIST_HEAD(&func->stack_node);
- func->state = KLP_DISABLED;
+ func->patched = 0;

return kobject_init_and_add(&func->kobj, &klp_ktype_func,
obj->kobj, func->old_name);
@@ -736,7 +735,7 @@ static int klp_init_object(struct klp_patch *patch, struct klp_object *obj)
if (!obj->funcs)
return -EINVAL;

- obj->state = KLP_DISABLED;
+ obj->patched = 0;

klp_find_object_module(obj);

@@ -775,7 +774,7 @@ static int klp_init_patch(struct klp_patch *patch)

mutex_lock(&klp_mutex);

- patch->state = KLP_DISABLED;
+ patch->enabled = 0;

ret = kobject_init_and_add(&patch->kobj, &klp_ktype_patch,
klp_root_kobj, patch->mod->name);
@@ -821,7 +820,7 @@ int klp_unregister_patch(struct klp_patch *patch)
goto out;
}

- if (patch->state == KLP_ENABLED) {
+ if (patch->enabled) {
ret = -EBUSY;
goto out;
}
@@ -882,13 +881,13 @@ static void klp_module_notify_coming(struct klp_patch *patch,
if (ret)
goto err;

- if (patch->state == KLP_DISABLED)
+ if (!patch->enabled)
return;

pr_notice("applying patch '%s' to loading module '%s'\n",
pmod->name, mod->name);

- ret = klp_enable_object(obj);
+ ret = klp_patch_object(obj);
if (!ret)
return;

@@ -903,15 +902,15 @@ static void klp_module_notify_going(struct klp_patch *patch,
struct module *pmod = patch->mod;
struct module *mod = obj->mod;

- if (patch->state == KLP_DISABLED)
- goto disabled;
+ if (!patch->enabled)
+ goto free;

pr_notice("reverting patch '%s' on unloading module '%s'\n",
pmod->name, mod->name);

- klp_disable_object(obj);
+ klp_unpatch_object(obj);

-disabled:
+free:
klp_free_object_loaded(obj);
}

--
2.1.0