livepatch: introduce atomic replace
Subsystems (2)
cover livepatch: introduce atomic replace
Hi,
In testing livepatch, I found that when doing cumulative patches, if a patched function is completed reverted by a subsequent patch (back to its original state) livepatch does not revert the funtion to its original state. Specifically, if patch A introduces a change to function 1, and patch B reverts the change to function 1 and introduces changes to say function 2 and 3 as well, the change that patch A introducd to function 1 is still present. This could be addressed by first completely removing patch A (disable and then rmmod) and then inserting patch B (insmod and enable), but this leaves an unpatched window. In discussing this issue with Josh on the kpatch mailing list, he mentioned that we could get 'atomic replace working properly', and that is the direction of this patchset:
https://www.redhat.com/archives/kpatch/2017-June/msg00005.html
Patches:
1) livepatch: Add klp_object and klp_func iterators Just a prep patch for the 'atomic revert' feature
2) livepatch: add atomic replace
Core feature
3) livepatch: Add a sysctl livepatch_mode for atomic replace Introduces a knob for enabling atomic replace. I hate knobs and perhaps its possible to default to cumulative replace? Although I suspect there are workflows relying on the existing behavior - I'm not sure. It may be desirable to associate the knob with the patch itself as in the 'immediate' flag, such that we don't introduce a global sysctl that likely would also need to built-in, if there are patches in the initrd.
Thanks,
-Jason
Jason Baron (3):
livepatch: Add klp_object and klp_func iterators
livepatch: add atomic replace
livepatch: Add a sysctl livepatch_mode for atomic revert
include/linux/livepatch.h | 118 ++++++++++++++++++++++++++++++-- kernel/livepatch/core.c | 154 ++++++++++++++++++++++++++++++++++++++++-- kernel/livepatch/core.h | 4 ++ kernel/livepatch/patch.c | 23 ++++--- kernel/livepatch/patch.h | 1 + kernel/livepatch/transition.c | 79 +++++++++++++++++++--- kernel/sysctl.c | 12 ++++ 7 files changed, 362 insertions(+), 29 deletions(-)
Signature · 1 line
2.6.1
PATCH 1/3 livepatch: Add klp_object and klp_func iterators
In preparation to introducing atomic replace, introduce iterators for klp_func and klp_object, such that objects and functions can be dynmically allocated (needed for atomic replace). Note that this patch is careful, not to grow the size of klp_func as that's the most common data structure. This patch is intended to effectively be a no-op until atomic replace is introduced.
--- include/linux/livepatch.h | 106 ++++++++++++++++++++++++++++++++++++++++-- kernel/livepatch/core.c | 25 +++++++--- kernel/livepatch/patch.c | 9 ++-- kernel/livepatch/transition.c | 18 ++++--- 4 files changed, 137 insertions(+), 21 deletions(-)
include/linux/livepatch.h +101 -5
@@ -24,6 +24,7 @@#include <linux/module.h> #include <linux/ftrace.h> #include <linux/completion.h> +#include <linux/list.h> #if IS_ENABLED (CONFIG_LIVEPATCH )
@@ -88,10 +89,23 @@ struct klp_func { };/** +* struct klp_func_no_op - internal object used to link no_op functions, which +avoids the need to bloat struct klp_func +* @orig_func: embeds struct klp_func +* @func_entry: used link struct klp_func_no_op to struct klp_object +*/ +struct klp_func_no_op { +struct klp_func orig_func; +struct list_head func_entry; +}; + +/** * 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 * @kobj: kobject for sysfs resources +* @func_list: head of list for struct klp_func_no_op +* @obj_entry: used to link struct klp_object to struct klp_patch * @mod: kernel module associated with the patched object * (NULL for vmlinux) * @patched: the object's funcs have been added to the klp_ops list
@@ -103,6 +117,8 @@ struct klp_object {/* internal */ struct kobject kobj; +struct list_head func_list; +struct list_head obj_entry;struct module *mod;bool patched; };
@@ -114,6 +130,7 @@ struct klp_object {* @immediate: patch all funcs immediately, bypassing safety mechanisms * @list: list node for global list of registered patches * @kobj: kobject for sysfs resources +* @obj_list: head of list for dynamically allocated struct klp_object * @enabled: the patch is enabled (but operation may be incomplete) * @finish: for waiting till it is safe to remove the patch module */
@@ -126,17 +143,96 @@ struct klp_patch {/* internal */ struct list_head list;struct kobject kobj; +struct list_head obj_list;bool enabled;struct completion finish; }; -#define klp_for_each_object (patch, obj) \ +struct obj_iter { +struct klp_object *obj; +struct list_head *obj_list_head; +struct list_head *obj_list_pos; +}; + +static inline struct klp_object *obj_iter_next (struct obj_iter *iter) +{ +struct klp_object *obj; + +if (iter->obj->funcs || iter->obj->name) { + obj = iter->obj; + iter->obj++; + }else { +if (iter->obj_list_pos == iter->obj_list_head) { + obj =NULL ; + }else { + obj = list_entry(iter->obj_list_pos, struct klp_object, + obj_entry); + iter->obj_list_pos = iter->obj_list_pos->next; + } + } + +return obj; +} + +static inline struct klp_object *obj_iter_init (struct klp_patch *patch, +struct obj_iter *iter) +{ + iter->obj = patch->objs; + iter->obj_list_head = &patch->obj_list; + iter->obj_list_pos = iter->obj_list_head->next; + +return obj_iter_next (iter); +} + +#define klp_for_each_object (patch, obj, iter) \ + for (obj = obj_iter_init(patch, iter); obj; obj = obj_iter_next(iter)) + +#define klp_for_each_object_core (patch, obj) \ for (obj = patch->objs; obj->funcs || obj->name; obj++) -#define klp_for_each_func (obj, func) \ - for (func = obj->funcs; \ - func->old_name || func->new_func || func->old_sympos; \ - func++) +struct func_iter { +struct klp_func *func; +struct list_head *func_list_head; +struct list_head *func_list_pos; +}; + +static inline struct klp_func *func_iter_next (struct func_iter *iter) +{ +struct klp_func *func; +struct klp_func_no_op *func_no_op; + +if (iter->func->old_name || iter->func->new_func || + iter->func->old_sympos) { + func = iter->func; + iter->func++; + }else { +if (iter->func_list_pos == iter->func_list_head) { + func =NULL ; + }else { + func_no_op =list_entry (iter->func_list_pos, + struct klp_func_no_op, + func_entry); + func = &func_no_op->orig_func; + iter->func_list_pos = iter->func_list_pos->next; + } + } + +return func; +} + +static inline struct klp_func *func_iter_init (struct klp_object *obj, +struct func_iter *iter) +{ + iter->func = obj->funcs; + iter->func_list_head = &obj->func_list; + iter->func_list_pos = iter->func_list_head->next; + +return func_iter_next (iter); +} + +#define klp_for_each_func (obj, func, iter) \ + for (func = func_iter_init(obj, iter); func; \ + func = func_iter_next(iter))int klp_register_patch (struct klp_patch *);int klp_unregister_patch (struct klp_patch *);
kernel/livepatch/core.c +18 -7
@@ -346,6 +346,7 @@ EXPORT_SYMBOL_GPL(klp_disable_patch);static int __klp_enable_patch (struct klp_patch *patch) {struct klp_object *obj; +struct obj_iter o_iter;int ret;if (klp_transition_patch)
@@ -384,7 +385,7 @@ static int __klp_enable_patch(struct klp_patch *patch)*/ smp_wmb (); -klp_for_each_object (patch, obj) { +klp_for_each_object (patch, obj, &o_iter) {if (!klp_is_object_loaded (obj))continue ;
@@ -571,10 +572,11 @@ static void klp_free_funcs_limited(struct klp_object *obj,static void klp_free_object_loaded (struct klp_object *obj) {struct klp_func *func; +struct func_iter f_iter; obj->mod =NULL ; -klp_for_each_func (obj, func) +klp_for_each_func (obj, func, &f_iter) func->old_addr =0 ; }
@@ -630,6 +632,7 @@ static int klp_init_object_loaded(struct klp_patch *patch, struct klp_object *obj) {struct klp_func *func; +struct func_iter f_iter;int ret;module_disable_ro (patch->mod);
@@ -642,7 +645,7 @@ static int klp_init_object_loaded(struct klp_patch *patch,arch_klp_init_object_loaded (patch, obj);module_enable_ro (patch->mod, true); -klp_for_each_func (obj, func) { +klp_for_each_func (obj, func, &f_iter) { ret =klp_find_object_symbol (obj->name, func->old_name, func->old_sympos, &func->old_addr);
@@ -672,6 +675,7 @@ static int klp_init_object_loaded(struct klp_patch *patch,static int klp_init_object (struct klp_patch *patch,struct klp_object *obj) {struct klp_func *func; +struct func_iter f_iter;int ret;const char *name;
@@ -689,7 +693,7 @@ static int klp_init_object(struct klp_patch *patch, struct klp_object *obj)if (ret)return ret; -klp_for_each_func (obj, func) { +klp_for_each_func (obj, func, &f_iter) { ret =klp_init_func (obj, func);if (ret) goto free;
@@ -712,6 +716,7 @@ static int klp_init_object(struct klp_patch *patch, struct klp_object *obj)static int klp_init_patch (struct klp_patch *patch) {struct klp_object *obj; +struct obj_iter o_iter;int ret;if (!patch->objs)
@@ -729,7 +734,11 @@ static int klp_init_patch(struct klp_patch *patch)return ret; } -klp_for_each_object (patch, obj) { +INIT_LIST_HEAD (&patch->obj_list); +klp_for_each_object_core (patch, obj) +INIT_LIST_HEAD (&obj->func_list); + +klp_for_each_object (patch, obj, &o_iter) { ret =klp_init_object (patch, obj);if (ret) goto free;
@@ -835,6 +844,7 @@ int klp_module_coming(struct module *mod)int ret;struct klp_patch *patch;struct klp_object *obj; +struct obj_iter o_iter;if (WARN_ON (mod->state !=MODULE_STATE_COMING ))return -EINVAL ;
@@ -848,7 +858,7 @@ int klp_module_coming(struct module *mod) mod->klp_alive = true;list_for_each_entry (patch, &klp_patches, list) { -klp_for_each_object (patch, obj) { +klp_for_each_object (patch, obj, &o_iter) {if (!klp_is_module (obj) ||strcmp (obj->name, mod->name))continue ;
@@ -904,6 +914,7 @@ void klp_module_going(struct module *mod) {struct klp_patch *patch;struct klp_object *obj; +struct obj_iter o_iter;if (WARN_ON (mod->state !=MODULE_STATE_GOING && mod->state !=MODULE_STATE_COMING ))
@@ -918,7 +929,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) { +klp_for_each_object (patch, obj, &o_iter) {if (!klp_is_module (obj) ||strcmp (obj->name, mod->name))continue ;
kernel/livepatch/patch.c +6 -3
@@ -238,8 +238,9 @@ static int klp_patch_func(struct klp_func *func)void klp_unpatch_object (struct klp_object *obj) {struct klp_func *func; +struct func_iter f_iter; -klp_for_each_func (obj, func) +klp_for_each_func (obj, func, &f_iter)if (func->patched)klp_unpatch_func (func);
@@ -249,12 +250,13 @@ void klp_unpatch_object(struct klp_object *obj)int klp_patch_object (struct klp_object *obj) {struct klp_func *func; +struct func_iter f_iter;int ret;if (WARN_ON (obj->patched))return -EINVAL ; -klp_for_each_func (obj, func) { +klp_for_each_func (obj, func, &f_iter) { ret =klp_patch_func (func);if (ret) {klp_unpatch_object (obj);
@@ -269,8 +271,9 @@ int klp_patch_object(struct klp_object *obj)void klp_unpatch_objects (struct klp_patch *patch) {struct klp_object *obj; +struct obj_iter o_iter; -klp_for_each_object (patch, obj) +klp_for_each_object (patch, obj, &o_iter)if (obj->patched)klp_unpatch_object (obj); }
kernel/livepatch/transition.c +12 -6
@@ -81,6 +81,8 @@ static void klp_complete_transition(void)struct task_struct *g, *task;unsigned int cpu;bool immediate_func = false; +struct obj_iter o_iter; +struct func_iter f_iter;if (klp_target_state ==KLP_UNPATCHED ) {/*
@@ -101,8 +103,8 @@ static void klp_complete_transition(void)if (klp_transition_patch->immediate) goto done; -klp_for_each_object (klp_transition_patch, obj) { -klp_for_each_func (obj, func) { +klp_for_each_object (klp_transition_patch, obj, &o_iter) { +klp_for_each_func (obj, func, &f_iter) { func->transition = false;if (func->immediate) immediate_func = true;
@@ -244,6 +246,8 @@ static int klp_check_stack(struct task_struct *task, char *err_buf)struct stack_trace trace;struct klp_object *obj;struct klp_func *func; +struct obj_iter o_iter; +struct func_iter f_iter;int ret; trace.skip =0 ;
@@ -259,10 +263,10 @@ static int klp_check_stack(struct task_struct *task, char *err_buf)return ret; } -klp_for_each_object (klp_transition_patch, obj) { +klp_for_each_object (klp_transition_patch, obj, &o_iter) {if (!obj->patched)continue ; -klp_for_each_func (obj, func) { +klp_for_each_func (obj, func, &f_iter) { ret =klp_check_stack_func (func, &trace);if (ret) { snprintf(err_buf, STACK_ERR_BUF_SIZE,
@@ -470,6 +474,8 @@ void klp_init_transition(struct klp_patch *patch, int state)unsigned int cpu;struct klp_object *obj;struct klp_func *func; +struct obj_iter o_iter; +struct func_iter f_iter;int initial_state = !state;WARN_ON_ONCE (klp_target_state !=KLP_UNDEFINED );
@@ -531,8 +537,8 @@ void klp_init_transition(struct klp_patch *patch, int state)* When unpatching, the funcs are already in the func_stack and so are * already visible to the ftrace handler. */ -klp_for_each_object (patch, obj) -klp_for_each_func (obj, func) +klp_for_each_object (patch, obj, &o_iter) +klp_for_each_func (obj, func, &f_iter) func->transition = true; }
Signature · 1 line
2.6.1
Petr Mladek
maintainerRe: [PATCH 1/3] livepatch: Add klp_object and klp_func iterators
Quoted 6 lines · Jason Baron
In preparation to introducing atomic replace, introduce iterators for
klp_funcandklp_object, such that objects and functions can be dynmically allocated (needed for atomic replace). Note that this patch is careful, not to grow the size ofklp_funcas that's the most common data structure. This patch is intended to effectively be a no-op until atomic replace is introduced.
We need to be careful here. The 3-level hiearachy is already complex enough. This patch adds several asymetric things:
+ the dynamically alocated structures are put into liked lists while the static ones are in arrays
+ the dynamically allocated func lists are linked using an external struct klp_func_no_op while
the dynamically allocated obj lists are linked using additional list_head in struct klp_object
Well, I agree that the combination of the iterators and extra no-op funcs allow to implement the atomic replace relatively easy and transparent way.
Another possibility would be to add some special flags to the affected patches on the stack. But this would require hacks in the ftrace handler, going through all patches in klp_try_complete_transition() and other functions. It looks more complicated.
I just wonder if we could clean this patch a bit.
Please, find some thoughts below.
Quoted 16 lines
include/linux/livepatch.h +1
-0@@ -24,6 +24,7 @@#include <linux/module.h> #include <linux/ftrace.h> #include <linux/completion.h> +#include <linux/list.h> #if IS_ENABLED (CONFIG_LIVEPATCH )@@ -88,10 +89,23 @@ struct klp_func { };
include/linux/livepatch.h struct klp_func { · line 100
Quoted 10 lines
include/linux/livepatch.h +9
-0/** +* struct klp_func_no_op - internal object used to link no_op functions, which +avoids the need to bloat struct klp_func +* @orig_func: embeds struct klp_func +* @func_entry: used link struct klp_func_no_op to struct klp_object +*/ +struct klp_func_no_op { +struct klp_func orig_func; +struct list_head func_entry; +};
I am not sure that this is worth it. It saves two pointers in struct klp-func that are mostly unused. But it adds one more asymmetry that would complicate maintaining the code.
struct klp_func is already quite big. Note that struct kobj is hidden there. I think that we could afford one more struct list_head there.
If people are concerned about the size, we could make this feature configurable at build time. People using this feature will benefit from the the ability to remove the replaced patches.
I also thought about using arrays also for the dynamic structures. But we would need to compute the size first. It might require another crazy code, especially when we allow to replace all patches and need to look for duplicates.
include/linux/livepatch.h struct klp_func { · line 108
Quoted 7 lines
include/linux/livepatch.h +3
-0+/** * 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 * @kobj: kobject for sysfs resources +* @func_list: head of list for struct klp_func_no_op +* @obj_entry: used to link struct klp_object to struct klp_patch
I would prefer to make the difference between the static and dynamic stuff more obvious. The generic names for both variants are confusing.
I would personally use "no_op" in the names of list_heads related to the no_op stuff. But then it would make more sense to add this in the next patch.
Well, I do not have strong opinion about it.
Quoted 42 lines
include/linux/livepatch.h +16
-1* @mod: kernel module associated with the patched object * (NULL for vmlinux) * @patched: the object's funcs have been added to the klp_ops list @@ -103,6 +117,8 @@ struct klp_object {/* internal */ struct kobject kobj; +struct list_head func_list; +struct list_head obj_entry;struct module *mod;bool patched; };@@ -114,6 +130,7 @@ struct klp_object {* @immediate: patch all funcs immediately, bypassing safety mechanisms * @list: list node for global list of registered patches * @kobj: kobject for sysfs resources +* @obj_list: head of list for dynamically allocated struct klp_object * @enabled: the patch is enabled (but operation may be incomplete) * @finish: for waiting till it is safe to remove the patch module */ @@ -126,17 +143,96 @@ struct klp_patch {/* internal */ struct list_head list;struct kobject kobj; +struct list_head obj_list;bool enabled;struct completion finish; }; -#define klp_for_each_object (patch, obj) \ +struct obj_iter { +struct klp_object *obj; +struct list_head *obj_list_head; +struct list_head *obj_list_pos; +}; + +static inline struct klp_object *obj_iter_next (struct obj_iter *iter) +{ +struct klp_object *obj; + +if (iter->obj->funcs || iter->obj->name) { + obj = iter->obj;
include/linux/livepatch.h struct klp_patch { · line 172
Quoted 10 lines
include/linux/livepatch.h +10
-0+ iter->obj++; + }else { +if (iter->obj_list_pos == iter->obj_list_head) { + obj =NULL ; + }else { + obj = list_entry(iter->obj_list_pos, struct klp_object, + obj_entry); + iter->obj_list_pos = iter->obj_list_pos->next; + } + }
This code might deserve some comments. Well, I wonder if we could make it working without the struct obj_iter.
If we could somehow distinguish the statically and dynamically allocated struct klp_object then we would know how to find the next one. I mean something like:
static inline struct klp_object *klp_next_object(struct klp_patch *patch,
struct klp_object *obj)
{
struct klp_object *next_obj = NULL;
/* Only statically defined objects has funcs array. */
if (obj->funcs) {
next_obj = obj + 1;
if (next_obj)
goto out;
/* Is there any dynamically allocated object? */
if (!list_empty(patch->obj_list)) {
next_obj = container_of(patch->obj_list.next,
struct klp_object,
obj_entry);
goto out;
}
/* This must be a dynamically allocated object. Is it the last one? */
if (obj->obj_entry.next != patch->obj_list)
next_obj = container_of(obj->obj_entry.next,
struct klp_object,
obj_entry);out:
return next_obj; }
Note that this is not even compile tested.
Best Regards,
Petr
Re: [PATCH 1/3] livepatch: Add klp_object and klp_func iterators
Hi Petr,
Quoted 68 lines · Petr Mladek
Quoted 6 lines · Jason Baron
In preparation to introducing atomic replace, introduce iterators for
klp_funcandklp_object, such that objects and functions can be dynmically allocated (needed for atomic replace). Note that this patch is careful, not to grow the size ofklp_funcas that's the most common data structure. This patch is intended to effectively be a no-op until atomic replace is introduced.We need to be careful here. The 3-level hiearachy is already complex enough. This patch adds several asymetric things:
+ the dynamically alocated structures are put into liked lists while the static ones are in arrays
+ the dynamically allocated func lists are linked using an external struct
klp_func_no_opwhile
the dynamically allocated obj lists are linked using additionallist_headin structklp_objectWell, I agree that the combination of the iterators and extra no-op funcs allow to implement the atomic replace relatively easy and transparent way.
Another possibility would be to add some special flags to the affected patches on the stack. But this would require hacks in the ftrace handler, going through all patches in
klp_try_complete_transition()and other functions. It looks more complicated.I just wonder if we could clean this patch a bit.
Please, find some thoughts below.Quoted 16 lines
include/linux/livepatch.h +1
-0@@ -24,6 +24,7 @@#include <linux/module.h> #include <linux/ftrace.h> #include <linux/completion.h> +#include <linux/list.h> #if IS_ENABLED (CONFIG_LIVEPATCH )@@ -88,10 +89,23 @@ struct klp_func { };include/linux/livepatch.h struct klp_func { · line 100
Quoted 10 lines
include/linux/livepatch.h +9
-0/** +* struct klp_func_no_op - internal object used to link no_op functions, which +avoids the need to bloat struct klp_func +* @orig_func: embeds struct klp_func +* @func_entry: used link struct klp_func_no_op to struct klp_object +*/ +struct klp_func_no_op { +struct klp_func orig_func; +struct list_head func_entry; +};I am not sure that this is worth it. It saves two pointers in struct klp-func that are mostly unused. But it adds one more asymmetry that would complicate maintaining the code.
struct
klp_funcis already quite big. Note that struct kobj is hidden there. I think that we could afford one more structlist_headthere.
Quoted 9 lines
If people are concerned about the size, we could make this feature configurable at build time. People using this feature will benefit from the the ability to remove the replaced patches.
I also thought about using arrays also for the dynamic structures. But we would need to compute the size first. It might require another crazy code, especially when we allow to replace all patches and need to look for duplicates.
Ok, I can remove the special 'struct klp_func_no_op', I agree it adds complexity to the code, and perhaps could be viewed as some later optimization if deemed a size bloat.
Quoted 12 lines
include/linux/livepatch.h struct klp_func { · line 108
Quoted 7 lines
include/linux/livepatch.h +3
-0+/** * 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 * @kobj: kobject for sysfs resources +* @func_list: head of list for struct klp_func_no_op +* @obj_entry: used to link struct klp_object to struct klp_patch I would prefer to make the difference between the static and dynamic stuff more obvious. The generic names for both variants are confusing.
Quoted 6 lines
I would personally use "
no_op" in the names oflist_headsrelated to theno_opstuff. But then it would make more sense to add this in the next patch.Well, I do not have strong opinion about it.
I think I avoided 'no_op' in the naming because I thought it maybe could be a more generic list, but in practice I'm only using it for the dynamic noops, so I like explicitly calling it the no_ops list to make its usage clear. I also think it can still live in 1/2 with the 'no_op' name, but if that's not ok, I could just rename it in 2/2.
Quoted 57 lines
Quoted 43 lines
include/linux/livepatch.h +16
-1* @mod: kernel module associated with the patched object * (NULL for vmlinux) * @patched: the object's funcs have been added to the klp_ops list @@ -103,6 +117,8 @@ struct klp_object {/* internal */ struct kobject kobj; +struct list_head func_list; +struct list_head obj_entry;struct module *mod;bool patched; };@@ -114,6 +130,7 @@ struct klp_object {* @immediate: patch all funcs immediately, bypassing safety mechanisms * @list: list node for global list of registered patches * @kobj: kobject for sysfs resources +* @obj_list: head of list for dynamically allocated struct klp_object * @enabled: the patch is enabled (but operation may be incomplete) * @finish: for waiting till it is safe to remove the patch module */ @@ -126,17 +143,96 @@ struct klp_patch {/* internal */ struct list_head list;struct kobject kobj; +struct list_head obj_list;bool enabled;struct completion finish; }; -#define klp_for_each_object (patch, obj) \ +struct obj_iter { +struct klp_object *obj; +struct list_head *obj_list_head; +struct list_head *obj_list_pos; +}; + +static inline struct klp_object *obj_iter_next (struct obj_iter *iter) +{ +struct klp_object *obj; + +if (iter->obj->funcs || iter->obj->name) { + obj = iter->obj;include/linux/livepatch.h struct klp_patch { · line 172
Quoted 10 lines
include/linux/livepatch.h +10
-0+ iter->obj++; + }else { +if (iter->obj_list_pos == iter->obj_list_head) { + obj =NULL ; + }else { + obj = list_entry(iter->obj_list_pos, struct klp_object, + obj_entry); + iter->obj_list_pos = iter->obj_list_pos->next; + } + }This code might deserve some comments. Well, I wonder if we could make it working without the struct
obj_iter.
Quoted 4 lines
If we could somehow distinguish the statically and dynamically allocated struct
klp_objectthen we would know how to find the next one. I mean something like:
yes, removing the obj_iter would be nice, and I think something like below could work. Another way to distinguish the objects might be to simply check if embedded list_head is used. That test would then also be able to be used for the function iterator (or I guess if its a no_op function, but I kind of like using the same test). I will try this out.
Thanks,
-Jason
Quoted 34 lines
static inline struct
klp_object*klp_next_object(structklp_patch*patch,
structklp_object*obj){ struct klp_object *next_obj = NULL; /* Only statically defined objects has funcs array. */ if (obj->funcs) { next_obj = obj + 1; if (next_obj) goto out; /* Is there any dynamically allocated object? */ if (!list_empty(patch->obj_list)) { next_obj = container_of(patch->obj_list.next, struct klp_object, obj_entry); goto out; } /* This must be a dynamically allocated object. Is it the last one? */ if (obj->obj_entry.next != patch->obj_list) next_obj = container_of(obj->obj_entry.next, struct klp_object, obj_entry);out:
return next_obj; }Note that this is not even compile tested.
Best Regards,
Petr
Petr Mladek
maintainerRe: [PATCH 1/3] livepatch: Add klp_object and klp_func iterators
Quoted 18 lines · Jason Baron
Quoted 17 lines · Petr Mladek
Quoted 7 lines · Jason Baron
+/**
* structklp_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
* @kobj: kobject for sysfs resources
+ * @func_list: head of list for structklp_func_no_op
+ * @obj_entry: used to link structklp_objectto structklp_patchI would prefer to make the difference between the static and dynamic stuff more obvious. The generic names for both variants are confusing.
I would personally use "
no_op" in the names oflist_headsrelated to theno_opstuff. But then it would make more sense to add this in the next patch.
Quoted 8 lines
Quoted 2 lines
Well, I do not have strong opinion about it.
I think I avoided '
no_op' in the naming because I thought it maybe could be a more generic list, but in practice I'm only using it for the dynamic noops, so I like explicitly calling it theno_opslist to make its usage clear. I also think it can still live in 1/2 with the 'no_op' name, but if that's not ok, I could just rename it in 2/2.
You could do only the needed preparation steps for the more complicated iterator in this patch. It might still handle only the static arrays. Then it might be extended in the 2nd patch together with adding the extra lists.
Best Regards,
Petr
PATCH 2/3 livepatch: add atomic replace
In reply to Jason BaronWhen doing cumulative patches, if patch A introduces a change to function 1, and patch B reverts the change to function 1 and introduces changes to say function 2 and 3 as well, the change that patch A introduced to function 1 is still present.
Introduce atomic replace, by first creating a 'no_op' klp_func for all the functions that are reverted by patch B. The reason that 'no_op' klp_funcs are created, instead of just unregistering directly from the ftrace function hook, is to ensure that the consistency model is properly preserved. By introducing the 'no_op' functions, 'atomic revert' leverages the existing consistency model code. Then, after transition to the new code, 'atomic revert' unregisters the ftrace handlers that are associated with the 'no_op' klp_funcs, such that that we run the original un-patched function with no additional no_op function overhead.
Since 'atomic replace' has completely replaced any previous livepatch modules, it explicitly disables the previous patch, in the example- patch A, such that the livepatch module associated with patch A can be completely removed (rmmod). Patch A is now in a permanently disabled state. But if patch A is removed from the kernel with rmmod, it can be re-inserted (insmod), and act as an atomic replace on top of patch B.
--- include/linux/livepatch.h | 8 ++- kernel/livepatch/core.c | 124 ++++++++++++++++++++++++++++++++++++++++++ kernel/livepatch/core.h | 4 ++ kernel/livepatch/patch.c | 14 +++-- kernel/livepatch/patch.h | 1 + kernel/livepatch/transition.c | 61 ++++++++++++++++++++- 6 files changed, 202 insertions(+), 10 deletions(-)
include/linux/livepatch.h +6 -2
@@ -49,6 +49,7 @@* @new_size: size of the new function * @patched: the func has been added to the klp_ops list * @transition: the func is currently being applied or reverted +* @no_op: this is a no_op function used to compelete revert a function * * The patched and transition variables define the func's patching state. When * patching, a func is always in one of the following states:
@@ -86,6 +87,7 @@ struct klp_func {unsigned long old_size, new_size;bool patched;bool transition; +bool no_op; };/**
@@ -132,6 +134,7 @@ struct klp_object {* @kobj: kobject for sysfs resources * @obj_list: head of list for dynamically allocated struct klp_object * @enabled: the patch is enabled (but operation may be incomplete) +* @replaced: the patch has been replaced an can not be re-enabled * @finish: for waiting till it is safe to remove the patch module */ struct klp_patch {
@@ -145,6 +148,7 @@ struct klp_patch {struct kobject kobj;struct list_head obj_list;bool enabled; +bool replaced;struct completion finish; };
@@ -201,8 +205,8 @@ static inline struct klp_func *func_iter_next(struct func_iter *iter)struct klp_func *func;struct klp_func_no_op *func_no_op; -if (iter->func->old_name || iter->func->new_func || - iter->func->old_sympos) { +if (iter->func && (iter->func->old_name || iter->func->new_func || + iter->func->old_sympos)) { func = iter->func; iter->func++; }else {
kernel/livepatch/core.c +124 -0
160 lines not on this page · raw message
kernel/livepatch/core.h +4 -0
@@ -1,6 +1,10 @@#ifndef _LIVEPATCH_CORE_H#define _LIVEPATCH_CORE_H +#include <linux/livepatch.h> +extern struct mutex klp_mutex; +void klp_patch_free_no_ops (struct klp_patch *patch); +#endif /* _LIVEPATCH_CORE_H */
kernel/livepatch/patch.c +9 -5
@@ -117,6 +117,8 @@ static void notrace klp_ftrace_handler(unsigned long ip, } } +if (func->no_op) + goto unlock;klp_arch_set_pc (regs, (unsigned long )func->new_func); unlock:preempt_enable_notrace ();
@@ -135,7 +137,7 @@ static unsigned long klp_get_ftrace_location(unsigned long faddr) }#endif -static void klp_unpatch_func (struct klp_func *func) +void klp_unpatch_func (struct klp_func *func,bool unregistered) {struct klp_ops *ops;
@@ -155,9 +157,11 @@ static void klp_unpatch_func(struct klp_func *func)if (WARN_ON (!ftrace_loc))return ; -WARN_ON (unregister_ftrace_function (&ops->fops)); -WARN_ON (ftrace_set_filter_ip (&ops->fops, ftrace_loc,1 ,0 )); - +if (!unregistered) { +WARN_ON (unregister_ftrace_function (&ops->fops)); +WARN_ON (ftrace_set_filter_ip (&ops->fops, ftrace_loc,1 , +0 )); + }list_del_rcu (&func->stack_node);list_del (&ops->node);kfree (ops);
@@ -242,7 +246,7 @@ void klp_unpatch_object(struct klp_object *obj)klp_for_each_func (obj, func, &f_iter)if (func->patched) -klp_unpatch_func (func); +klp_unpatch_func (func, false); obj->patched = false; }
kernel/livepatch/patch.h +1 -0
@@ -29,5 +29,6 @@ struct klp_ops *klp_find_ops(unsigned long old_addr);int klp_patch_object (struct klp_object *obj);void klp_unpatch_object (struct klp_object *obj);void klp_unpatch_objects (struct klp_patch *patch); +void klp_unpatch_func (struct klp_func *func,bool unregistered);#endif /* _LIVEPATCH_PATCH_H */
kernel/livepatch/transition.c +58 -3
101 lines not on this page · raw message
Signature · 1 line
2.6.1
Petr Mladek
maintainerRe: [PATCH 2/3] livepatch: add atomic replace
include/linux/livepatch.h struct klp_object { · line 137
Quoted 12 lines · Jason Baron
Introduce atomic replace, by first creating a '
no_op'klp_funcfor all the functions that are reverted by patch B.include/linux/livepatch.h +1
-0@@ -132,6 +134,7 @@ struct klp_object {* @kobj: kobject for sysfs resources * @obj_list: head of list for dynamically allocated struct klp_object * @enabled: the patch is enabled (but operation may be incomplete) +* @replaced: the patch has been replaced an can not be re-enabled
Is there any particular reason why the removed patch could not longer be enabled? The user could get around this by removing the replaced patch (rmmod) and applying it again (insmod) but...
include/linux/livepatch.h static inline struct klp_func *func_iter_next(struct func_iter *iter) · line 209
Quoted 11 lines
include/linux/livepatch.h +2
-2* @finish: for waiting till it is safe to remove the patch module */ struct klp_patch {@@ -201,8 +205,8 @@ static inline struct klp_func *func_iter_next(struct func_iter *iter)struct klp_func *func;struct klp_func_no_op *func_no_op; -if (iter->func->old_name || iter->func->new_func || - iter->func->old_sympos) { +if (iter->func && (iter->func->old_name || iter->func->new_func || + iter->func->old_sympos)) {
I guess that this check should already be in the previous patch.
Quoted 16 lines
include/linux/livepatch.h +0
-0func = iter->func; iter->func++; }else {kernel/livepatch/core.c +8
-0@@ -602,6 +605,118 @@ static void klp_free_patch(struct klp_patch *patch) + +static int klp_init_patch_no_ops (struct klp_patch *patch) +{ +struct klp_object *obj, *prev_obj, *new_obj; +struct klp_func *prev_func, *func; +struct klp_func_no_op *new; +struct klp_patch *prev_patch; +struct obj_iter o_iter, prev_o_iter;
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 622
Quoted 10 lines
kernel/livepatch/core.c +10
-0+struct func_iter prev_f_iter, f_iter; +bool found, mod; + +if (patch->list.prev == &klp_patches) +return 0 ; + + prev_patch =list_prev_entry (patch, list); +klp_for_each_object (prev_patch, prev_obj, &prev_o_iter) { +if (!klp_is_object_loaded (prev_obj)) +continue ;
We must create the no_op object/function structures also for no-yet-loaded modules. The transaction might take a long time. The module might be loaded in the meantime. It must be able to enable the stack of patches completely, including the no_op ones, see klp_module_coming().
Note that the module must be patched even temporarily because some (non-migrated) processes might still use the semantic required by the to-be-removed patches.
This is actually one big advantage of the dynamically allocated no_op functions. The infrastructure for the coming/going modules should just work. The same is true for klp_reverse_transition().
Quoted 15 lines
kernel/livepatch/core.c +15
-0+ +klp_for_each_func (prev_obj, prev_func, &prev_f_iter) { + found = false; +klp_for_each_object (patch, obj, &o_iter) { +klp_for_each_func (obj, func, &f_iter) { +if ((strcmp (prev_func->old_name, + func->old_name) ==0 ) && + (prev_func->old_sympos == + func->old_sympos)) { + found = true; +break ; + } + } +if (found) +break ;
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 674
Quoted 10 lines
kernel/livepatch/core.c +10
-0+ } +if (found) +continue ; + + new =kmalloc (sizeof (*new),GFP_KERNEL ); +if (!new) +return -ENOMEM ; + new->orig_func = *prev_func; + new->orig_func.old_name = prev_func->old_name; + new->orig_func.new_func =NULL ;
This is OK if the operation replaces all older patches. Otherwise, you would need to store here the address from the patch down the stack.
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 677
Quoted 3 lines
kernel/livepatch/core.c +3
-0+ new->orig_func.old_sympos = prev_func->old_sympos; + new->orig_func.immediate = prev_func->immediate; + new->orig_func.old_addr = prev_func->old_addr;
Hmm, this should be
new->orig_func.old_addr = prev_func->new_func;
klp_check_stack_func() should check the address of the old function that is currently running. It is the variant of the function that is on top of stack.
I think that we actually have bug in the current livepatch code because old_addr always points to the original function!!! I am going to look at it.
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 679
Quoted 2 lines
kernel/livepatch/core.c +2
-0+INIT_LIST_HEAD (&new->orig_func.stack_node); + new->orig_func.old_size = prev_func->old_size;
Same here. It should be the size of the currently used function implementation (from the last active patch).
Quoted 18 lines
kernel/livepatch/core.c +18
-0+ new->orig_func.new_size =0 ; + new->orig_func.no_op = true; + new->orig_func.patched = false; + new->orig_func.transition = false; + found = false; + mod =klp_is_module (prev_obj); +klp_for_each_object (patch, obj, &o_iter) { +if (mod) { +if (klp_is_module (obj) && +strcmp (prev_obj->name, + obj->name) ==0 ) { + found = true; +break ; + } + }else if (!klp_is_module (obj)) { + found = true; +break ; + }
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 707
Quoted 10 lines
kernel/livepatch/core.c +10
-0+ } +if (found) { +list_add (&new->func_entry, &obj->func_list); + }else { + new_obj =kmalloc (sizeof (*new_obj),GFP_KERNEL ); +if (!new_obj) +return -ENOMEM ; + new_obj->name = prev_obj->name; + new_obj->funcs =NULL ; + new_obj->mod = prev_obj->mod;
The new temporary object should be connected with the new patch. I mean with patch->mod.
Another question is what to do with the kobjects in the dynamically allocated klp_object and klp_func. I do not have strong opinion here.
One thing is that the kobject hierarchy allows to find *patch from *obj and *obj from *func pointers. I guess that we do not rely on this in the current livepatch code. But it is a nice trick that we use in Kgraft.
Also userspace might use the related /sys/ hierarchy to monitor the livepatching situation. If we do not create the kobjects, we will hide some information.
Finally, please try to split this function into more pieces. I wonder if we could separate the code for each level (patch, object, func).
Quoted 15 lines
kernel/livepatch/core.c +12
-0+ new_obj->patched = false; +INIT_LIST_HEAD (&new_obj->func_list); +INIT_LIST_HEAD (&new_obj->obj_entry); +list_add (&new->func_entry, &new_obj->func_list); +list_add (&new_obj->obj_entry, &patch->obj_list); + } + } + } + +return 0 ; +} +static int klp_init_func (struct klp_object *obj,struct klp_func *func) {if (!func->old_name || !func->new_func)
[...]
kernel/livepatch/patch.c static void notrace klp_ftrace_handler(unsigned long ip, · line 121
Quoted 10 lines
kernel/livepatch/patch.c +2
-0@@ -117,6 +117,8 @@ static void notrace klp_ftrace_handler(unsigned long ip, } } +if (func->no_op) + goto unlock;
JFYI, this means that we will use the original (unpatched) code. It is perfectly fine if we new patch replaces all older ones. It is wrong in the current concept where you want to replace only the last patch on the stack. See above my comments about orig_func.new_func.
To make it clear, I am all for replacing all patches on the stack. IMHO, it is much more practical. The result is well defined.
If you remove only the last patch, the result depends on the other patches on the stack which is harder to predict. IMHO, replacing all patches is a better semantic.
Quoted 15 lines
kernel/livepatch/patch.c +1
-1klp_arch_set_pc (regs, (unsigned long )func->new_func); unlock:preempt_enable_notrace ();@@ -135,7 +137,7 @@ static unsigned long klp_get_ftrace_location(unsigned long faddr) }#endif -static void klp_unpatch_func (struct klp_func *func) +void klp_unpatch_func (struct klp_func *func,bool unregistered) {struct klp_ops *ops;@@ -155,9 +157,11 @@ static void klp_unpatch_func(struct klp_func *func)if (WARN_ON (!ftrace_loc))return ;
kernel/livepatch/patch.c static void klp_unpatch_func(struct klp_func *func) · line 165
Quoted 10 lines
kernel/livepatch/patch.c +5
-3-WARN_ON (unregister_ftrace_function (&ops->fops)); -WARN_ON (ftrace_set_filter_ip (&ops->fops, ftrace_loc,1 ,0 )); - +if (!unregistered) { +WARN_ON (unregister_ftrace_function (&ops->fops)); +WARN_ON (ftrace_set_filter_ip (&ops->fops, ftrace_loc,1 , +0 )); + }list_del_rcu (&func->stack_node);
IMHO, we should rather unpatch the replaced patches before we remove the temporary no_op stuff. Then this should not be needed, see below.
Quoted 17 lines
kernel/livepatch/patch.c +0
-0list_del (&ops->node);kfree (ops);kernel/livepatch/transition.c +1
-0@@ -70,6 +72,7 @@ static void klp_synchronize_transition(void)schedule_on_each_cpu (klp_sync); } +/* * The transition to the target patch state is complete. Clean up the data * structures. @@ -81,8 +84,32 @@ static void klp_complete_transition(void)struct task_struct *g, *task;unsigned int cpu;
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 95
Quoted 10 lines
kernel/livepatch/transition.c +7
-0bool immediate_func = false; +bool no_op = false;struct obj_iter o_iter;struct func_iter f_iter; +unsigned long ftrace_loc; +struct klp_ops *ops; +struct klp_patch *prev_patch; + +/* remove ftrace hook for all no_op functions. */ +if (klp_target_state ==KLP_PATCHED ) {
IMHO, this is the right place to call:
klp_unpatch_objects(prev_patch);
It will just remove the function from the func_stack(s). All stacks have at least two functions: the replaced one and one from the new patch (real or no_op). It should keep the one from the new patch on top of the stack.
Quoted 4 lines
kernel/livepatch/transition.c +4
-0+klp_for_each_object (klp_transition_patch, obj, &o_iter) { +klp_for_each_func (obj, func, &f_iter) { +if (!func->no_op) +continue ;
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 109
Quoted 10 lines
kernel/livepatch/transition.c +10
-0+ + ops =klp_find_ops (func->old_addr); +if (WARN_ON (!ops)) +continue ; + ftrace_loc = func->old_addr; +WARN_ON (unregister_ftrace_function (&ops->fops)); +WARN_ON (ftrace_set_filter_ip (&ops->fops, + ftrace_loc, +1 ,0 )); + no_op = true;
I wonder if we could reuse klp_unpatch_object() and klp_unpatch_function() here. We could add a no_op parameter to unpatch only the no_op stuff.
Quoted 7 lines
kernel/livepatch/transition.c +3
-0+ } + } + }if (klp_target_state ==KLP_UNPATCHED ) {/*
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 169
Quoted 9 lines
kernel/livepatch/transition.c +6
-0@@ -132,6 +161,24 @@ static void klp_complete_transition(void) } done: +/* remove and free any no_op functions */ +if (no_op && klp_target_state ==KLP_PATCHED ) { + prev_patch =list_prev_entry (klp_transition_patch, list); +if (prev_patch->enabled) { +klp_unpatch_objects (prev_patch); + prev_patch->enabled = false;
Hmm, the patch is normally marked disabled before the transition starts, see __klp_disable_patch().
We should do this in __klp_enable_patch(). It needs to be handled also in klp_reverse_transition().
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 178
Quoted 9 lines
kernel/livepatch/transition.c +9
-0+ prev_patch->replaced = true; +module_put (prev_patch->mod); + } +klp_for_each_object (klp_transition_patch, obj, &o_iter) { +klp_for_each_func (obj, func, &f_iter) { +if (func->no_op) +klp_unpatch_func (func, true); + } + }
This should be done earlier. See above the hint about adding no_op parameter to klp_unpatch_object() and klp_uptach_func() and reusing their code.
My intention is that we should ideally do the same actions in the same locations as we already do for normal patches. I mean manipulating the various flags, ftrace handlers, and other per-patch/object/func values. It should help to simply review and even reuse some more code.
Quoted 14 lines
kernel/livepatch/transition.c +6
-1+klp_patch_free_no_ops (klp_transition_patch); + } + klp_target_state =KLP_UNDEFINED ; klp_transition_patch =NULL ; }@@ -204,10 +251,18 @@ static int klp_check_stack_func(struct klp_func *func,if (klp_target_state ==KLP_UNPATCHED ) {/* * Check for the to-be-unpatched function -* (the func itself). +* (the func itself). If we're unpatching +* a no-op, then we're running the original +* function. We never 'patch' a no-op function,
kernel/livepatch/transition.c static int klp_check_stack_func(struct klp_func *func, · line 264
Quoted 10 lines
kernel/livepatch/transition.c +7
-2+* since we just remove the ftrace hook. */ - func_addr = (unsigned long )func->new_func; - func_size = func->new_size; +if (func->no_op) { + func_addr = (unsigned long )func->old_addr; + func_size = func->old_size; + }else { + func_addr = (unsigned long )func->new_func; + func_size = func->new_size;
This looks wrong. It should not be needed if we set new_func and new_addr correctly in klp_init_patch_no_ops().
Thanks a lot for working on this feature. You did great job. It seems that it was functional. Most of my hints should just help to better integrate it into the existing code.
Best Regards,
Petr
Petr Mladek
maintainerRe: [PATCH 2/3] livepatch: add atomic replace
Quoted 62 lines · Petr Mladek
Quoted 38 lines · Jason Baron
kernel/livepatch/core.c +33
-0@@ -602,6 +605,118 @@ static void klp_free_patch(struct klp_patch *patch) + +static int klp_init_patch_no_ops (struct klp_patch *patch) +{ +struct klp_object *obj, *prev_obj, *new_obj; +struct klp_func *prev_func, *func; +struct klp_func_no_op *new; +struct klp_patch *prev_patch; +struct obj_iter o_iter, prev_o_iter; +struct func_iter prev_f_iter, f_iter; +bool found, mod; + +if (patch->list.prev == &klp_patches) +return 0 ; + + prev_patch =list_prev_entry (patch, list); +klp_for_each_object (prev_patch, prev_obj, &prev_o_iter) { +if (!klp_is_object_loaded (prev_obj)) +continue ; + +klp_for_each_func (prev_obj, prev_func, &prev_f_iter) { + found = false; +klp_for_each_object (patch, obj, &o_iter) { +klp_for_each_func (obj, func, &f_iter) { +if ((strcmp (prev_func->old_name, + func->old_name) ==0 ) && + (prev_func->old_sympos == + func->old_sympos)) { + found = true; +break ; + } + } +if (found) +break ;kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 647
Quoted 10 lines
kernel/livepatch/core.c +10
-0+ } +if (found) +continue ; + + new =kmalloc (sizeof (*new),GFP_KERNEL ); +if (!new) +return -ENOMEM ; + new->orig_func = *prev_func; + new->orig_func.old_name = prev_func->old_name; + new->orig_func.new_func =NULL ;This is OK if the operation replaces all older patches. Otherwise, you would need to store here the address from the patch down the stack.
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 677
Quoted 3 lines
kernel/livepatch/core.c +3
-0+ new->orig_func.old_sympos = prev_func->old_sympos; + new->orig_func.immediate = prev_func->immediate; + new->orig_func.old_addr = prev_func->old_addr;Hmm, this should be
new->orig_func.old_addr = prev_func->new_func;
Quoted 7 lines
klp_check_stack_func()should check the address of the old function that is currently running. It is the variant of the function that is on top of stack.I think that we actually have bug in the current livepatch code because
old_addralways points to the original function!!! I am going to look at it.
I take this back. old_addr and old_size points to the original (unpatched) code. The values are the same in all patches for the same function. klp_check_stack_func() use this information only when there is only one patch on the func_stack. Otherwise, it checks new_func, new_size from the previous patch on the stack.
By other words, you assign old_addr and old_size correctly. Also the livepatch handle this correctly. Ufff :-)
Best Regards,
Petr
Re: [PATCH 2/3] livepatch: add atomic replace
In reply to Petr MladekQuoted 4 lines · Petr Mladek
Quoted 2 lines · Jason Baron
Introduce atomic replace, by first creating a '
no_op'klp_funcfor all the functions that are reverted by patch B.
Quoted 15 lines
include/linux/livepatch.h struct klp_object { · line 137
Quoted 9 lines
include/linux/livepatch.h +1
-0@@ -132,6 +134,7 @@ struct klp_object {* @kobj: kobject for sysfs resources * @obj_list: head of list for dynamically allocated struct klp_object * @enabled: the patch is enabled (but operation may be incomplete) +* @replaced: the patch has been replaced an can not be re-enabled Is there any particular reason why the removed patch could not longer be enabled? The user could get around this by removing the replaced patch (rmmod) and applying it again (insmod) but...
In the current model patches are enabled and disabled in a 'stacked' manner. So if the topmost patch is enabled then all previous patches are also enabled. This atomic replace feature doesn't fit this model as it disables all previous patches, since its replacing all patches that came before it.
We could allow the previous patches to be re-enabled, but if they are 'replace' patches they would have to re-calculate the no-op patches based on the last enabled patch. This breaks the 'stacked' model and seemed to me to add extra complexity without really adding any new functionality. As you pointed out, and I have verified in testing, the model with replace is to effectively load the new module, and if you want to go back to an older one you rmmod*( it and insmod() it again, and you thus effectively only have 1 patch (except during transitions) loaded at a time. So I feel like disabling all previous patches makes this operation model explicit.
Quoted 8 lines
Quoted 8 lines
include/linux/livepatch.h +0
-0* @finish: for waiting till it is safe to remove the patch module */ struct klp_patch {@@ -201,8 +205,8 @@ static inline struct klp_func *func_iter_next(struct func_iter *iter)struct klp_func *func;struct klp_func_no_op *func_no_op;
Quoted 7 lines
include/linux/livepatch.h static inline struct klp_func *func_iter_next(struct func_iter *iter) · line 209
Quoted 4 lines
include/linux/livepatch.h +2
-2-if (iter->func->old_name || iter->func->new_func || - iter->func->old_sympos) { +if (iter->func && (iter->func->old_name || iter->func->new_func || + iter->func->old_sympos)) {I guess that this check should already be in the previous patch.
I've removed this check now based on your iterator suggestions. I've updated the series and will send out v2 shortly.
Quoted 34 lines
Quoted 17 lines
include/linux/livepatch.h +0
-0func = iter->func; iter->func++; }else {kernel/livepatch/core.c +8
-0@@ -602,6 +605,118 @@ static void klp_free_patch(struct klp_patch *patch) + +static int klp_init_patch_no_ops (struct klp_patch *patch) +{ +struct klp_object *obj, *prev_obj, *new_obj; +struct klp_func *prev_func, *func; +struct klp_func_no_op *new; +struct klp_patch *prev_patch; +struct obj_iter o_iter, prev_o_iter;kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 622
Quoted 10 lines
kernel/livepatch/core.c +10
-0+struct func_iter prev_f_iter, f_iter; +bool found, mod; + +if (patch->list.prev == &klp_patches) +return 0 ; + + prev_patch =list_prev_entry (patch, list); +klp_for_each_object (prev_patch, prev_obj, &prev_o_iter) { +if (!klp_is_object_loaded (prev_obj)) +continue ;We must create the
no_opobject/function structures also for no-yet-loaded modules. The transaction might take a long time. The module might be loaded in the meantime. It must be able to enable the stack of patches completely, including theno_opones, seeklp_module_coming().
Quoted 8 lines
Note that the module must be patched even temporarily because some (non-migrated) processes might still use the semantic required by the to-be-removed patches.
This is actually one big advantage of the dynamically allocated
no_opfunctions. The infrastructure for the coming/going modules should just work. The same is true forklp_reverse_transition().
Agreed.
Quoted 22 lines
Quoted 22 lines
kernel/livepatch/core.c +20
-0+ +klp_for_each_func (prev_obj, prev_func, &prev_f_iter) { + found = false; +klp_for_each_object (patch, obj, &o_iter) { +klp_for_each_func (obj, func, &f_iter) { +if ((strcmp (prev_func->old_name, + func->old_name) ==0 ) && + (prev_func->old_sympos == + func->old_sympos)) { + found = true; +break ; + } + } +if (found) +break ; + } +if (found) +continue ; + + new =kmalloc (sizeof (*new),GFP_KERNEL );
Quoted 9 lines
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 674
Quoted 5 lines
kernel/livepatch/core.c +5
-0+if (!new) +return -ENOMEM ; + new->orig_func = *prev_func; + new->orig_func.old_name = prev_func->old_name; + new->orig_func.new_func =NULL ;This is OK if the operation replaces all older patches. Otherwise, you would need to store here the address from the patch down the stack.
Yes, the intention is to replace all older patches.
Quoted 18 lines
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 677
Quoted 4 lines
kernel/livepatch/core.c +3
-0+ new->orig_func.old_sympos = prev_func->old_sympos; + new->orig_func.immediate = prev_func->immediate; + new->orig_func.old_addr = prev_func->old_addr;Hmm, this should be
new->orig_func.old_addr = prev_func->new_func;
klp_check_stack_func()should check the address of the old function that is currently running. It is the variant of the function that is on top of stack.I think that we actually have bug in the current livepatch code because
old_addralways points to the original function!!! I am going to look at it.
Quoted 6 lines
kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 679
Quoted 2 lines
kernel/livepatch/core.c +2
-0+INIT_LIST_HEAD (&new->orig_func.stack_node); + new->orig_func.old_size = prev_func->old_size;Same here. It should be the size of the currently used function implementation (from the last active patch).
I think your follow-up mail here pointed out this is fine as is.
Quoted 36 lines
Quoted 19 lines
kernel/livepatch/core.c +18
-0+ new->orig_func.new_size =0 ; + new->orig_func.no_op = true; + new->orig_func.patched = false; + new->orig_func.transition = false; + found = false; + mod =klp_is_module (prev_obj); +klp_for_each_object (patch, obj, &o_iter) { +if (mod) { +if (klp_is_module (obj) && +strcmp (prev_obj->name, + obj->name) ==0 ) { + found = true; +break ; + } + }else if (!klp_is_module (obj)) { + found = true; +break ; + }kernel/livepatch/core.c static void klp_free_patch(struct klp_patch *patch) · line 707
Quoted 10 lines
kernel/livepatch/core.c +10
-0+ } +if (found) { +list_add (&new->func_entry, &obj->func_list); + }else { + new_obj =kmalloc (sizeof (*new_obj),GFP_KERNEL ); +if (!new_obj) +return -ENOMEM ; + new_obj->name = prev_obj->name; + new_obj->funcs =NULL ; + new_obj->mod = prev_obj->mod;The new temporary object should be connected with the new patch. I mean with
patch->mod.Another question is what to do with the kobjects in the dynamically allocated
klp_objectandklp_func. I do not have strong opinion here.
Quoted 9 lines
One thing is that the kobject hierarchy allows to find *patch from *obj and *obj from *func pointers. I guess that we do not rely on this in the current livepatch code. But it is a nice trick that we use in Kgraft.
Also userspace might use the related
/sys/hierarchy to monitor the livepatching situation. If we do not create the kobjects, we will hide some information.
I've added the kobj initialization stuff in v2.
Quoted 3 lines
Finally, please try to split this function into more pieces. I wonder if we could separate the code for each level (patch, object, func).
I'm not making use of the klp_init_object() and klp_init_func() there...
Quoted 37 lines
Quoted 17 lines
kernel/livepatch/core.c +12
-0+ new_obj->patched = false; +INIT_LIST_HEAD (&new_obj->func_list); +INIT_LIST_HEAD (&new_obj->obj_entry); +list_add (&new->func_entry, &new_obj->func_list); +list_add (&new_obj->obj_entry, &patch->obj_list); + } + } + } + +return 0 ; +} +static int klp_init_func (struct klp_object *obj,struct klp_func *func) {if (!func->old_name || !func->new_func)[...]
kernel/livepatch/patch.c static void notrace klp_ftrace_handler(unsigned long ip, · line 121
Quoted 10 lines
kernel/livepatch/patch.c +2
-0@@ -117,6 +117,8 @@ static void notrace klp_ftrace_handler(unsigned long ip, } } +if (func->no_op) + goto unlock;JFYI, this means that we will use the original (unpatched) code. It is perfectly fine if we new patch replaces all older ones. It is wrong in the current concept where you want to replace only the last patch on the stack. See above my comments about orig_func.new_func.
Quoted 6 lines
To make it clear, I am all for replacing all patches on the stack. IMHO, it is much more practical. The result is well defined.
If you remove only the last patch, the result depends on the other patches on the stack which is harder to predict. IMHO, replacing all patches is a better semantic.
Agreed. I've updated the semantic such that a 'replace' patch replaces all previous patches on the stack. It walks the patch list backwards until it finds the previous 'replace' patch (since that replaced everything before itself).
Quoted 18 lines
Quoted 18 lines
kernel/livepatch/patch.c +1
-1klp_arch_set_pc (regs, (unsigned long )func->new_func); unlock:preempt_enable_notrace ();@@ -135,7 +137,7 @@ static unsigned long klp_get_ftrace_location(unsigned long faddr) }#endif -static void klp_unpatch_func (struct klp_func *func) +void klp_unpatch_func (struct klp_func *func,bool unregistered) {struct klp_ops *ops;@@ -155,9 +157,11 @@ static void klp_unpatch_func(struct klp_func *func)if (WARN_ON (!ftrace_loc))return ;
Quoted 14 lines
kernel/livepatch/patch.c static void klp_unpatch_func(struct klp_func *func) · line 165
Quoted 9 lines
kernel/livepatch/patch.c +5
-3-WARN_ON (unregister_ftrace_function (&ops->fops)); -WARN_ON (ftrace_set_filter_ip (&ops->fops, ftrace_loc,1 ,0 )); - +if (!unregistered) { +WARN_ON (unregister_ftrace_function (&ops->fops)); +WARN_ON (ftrace_set_filter_ip (&ops->fops, ftrace_loc,1 , +0 )); + }list_del_rcu (&func->stack_node);IMHO, we should rather unpatch the replaced patches before we remove the temporary
no_opstuff. Then this should not be needed, see below.
Ok, make sense. I've added this to v2 patches.
Quoted 49 lines
Quoted 18 lines
kernel/livepatch/patch.c +0
-0list_del (&ops->node);kfree (ops);kernel/livepatch/transition.c +1
-0@@ -70,6 +72,7 @@ static void klp_synchronize_transition(void)schedule_on_each_cpu (klp_sync); } +/* * The transition to the target patch state is complete. Clean up the data * structures. @@ -81,8 +84,32 @@ static void klp_complete_transition(void)struct task_struct *g, *task;unsigned int cpu;kernel/livepatch/transition.c static void klp_complete_transition(void) · line 95
Quoted 10 lines
kernel/livepatch/transition.c +7
-0bool immediate_func = false; +bool no_op = false;struct obj_iter o_iter;struct func_iter f_iter; +unsigned long ftrace_loc; +struct klp_ops *ops; +struct klp_patch *prev_patch; + +/* remove ftrace hook for all no_op functions. */ +if (klp_target_state ==KLP_PATCHED ) {IMHO, this is the right place to call:
klp_unpatch_objects(prev_patch);It will just remove the function from the
func_stack(s). All stacks have at least two functions: the replaced one and one from the new patch (real orno_op). It should keep the one from the new patch on top of the stack.Quoted 10 lines
kernel/livepatch/transition.c +10
-0+klp_for_each_object (klp_transition_patch, obj, &o_iter) { +klp_for_each_func (obj, func, &f_iter) { +if (!func->no_op) +continue ; + + ops =klp_find_ops (func->old_addr); +if (WARN_ON (!ops)) +continue ; + ftrace_loc = func->old_addr; +WARN_ON (unregister_ftrace_function (&ops->fops));
Quoted 9 lines
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 109
Quoted 4 lines
kernel/livepatch/transition.c +4
-0+WARN_ON (ftrace_set_filter_ip (&ops->fops, + ftrace_loc, +1 ,0 )); + no_op = true;I wonder if we could reuse
klp_unpatch_object()andklp_unpatch_function()here. We could add ano_opparameter to unpatch only theno_opstuff.
Ok, added in v2.
Quoted 10 lines
Quoted 10 lines
kernel/livepatch/transition.c +3
-0+ } + } + }if (klp_target_state ==KLP_UNPATCHED ) {/* @@ -132,6 +161,24 @@ static void klp_complete_transition(void) }
Quoted 11 lines
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 169
Quoted 7 lines
kernel/livepatch/transition.c +6
-0done: +/* remove and free any no_op functions */ +if (no_op && klp_target_state ==KLP_PATCHED ) { + prev_patch =list_prev_entry (klp_transition_patch, list); +if (prev_patch->enabled) { +klp_unpatch_objects (prev_patch); + prev_patch->enabled = false;Hmm, the patch is normally marked disabled before the transition starts, see
__klp_disable_patch().
I was treating this a special case for atomic replace where it disables all previous patches, so its not in the usual __klp_disable_patch() spot.
Quoted 3 lines
We should do this in
__klp_enable_patch(). It needs to be handled also inklp_reverse_transition().
Can you clarify this a bit more for me?
Quoted 15 lines
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 178
Quoted 10 lines
kernel/livepatch/transition.c +9
-0+ prev_patch->replaced = true; +module_put (prev_patch->mod); + } +klp_for_each_object (klp_transition_patch, obj, &o_iter) { +klp_for_each_func (obj, func, &f_iter) { +if (func->no_op) +klp_unpatch_func (func, true); + } + }This should be done earlier. See above the hint about adding
no_opparameter toklp_unpatch_object()andklp_uptach_func()and reusing their code.
Quoted 6 lines
My intention is that we should ideally do the same actions in the same locations as we already do for normal patches. I mean manipulating the various flags, ftrace handlers, and other per-patch/object/func values. It should help to simply review and even reuse some more code.
Ok, I've tried to address this in v2.
Quoted 20 lines
Quoted 20 lines
kernel/livepatch/transition.c +8
-3+klp_patch_free_no_ops (klp_transition_patch); + } + klp_target_state =KLP_UNDEFINED ; klp_transition_patch =NULL ; }@@ -204,10 +251,18 @@ static int klp_check_stack_func(struct klp_func *func,if (klp_target_state ==KLP_UNPATCHED ) {/* * Check for the to-be-unpatched function -* (the func itself). +* (the func itself). If we're unpatching +* a no-op, then we're running the original +* function. We never 'patch' a no-op function, +* since we just remove the ftrace hook. */ - func_addr = (unsigned long )func->new_func; - func_size = func->new_size; +if (func->no_op) {
Quoted 9 lines
kernel/livepatch/transition.c static int klp_check_stack_func(struct klp_func *func, · line 264
Quoted 5 lines
kernel/livepatch/transition.c +5
-0+ func_addr = (unsigned long )func->old_addr; + func_size = func->old_size; + }else { + func_addr = (unsigned long )func->new_func; + func_size = func->new_size;This looks wrong. It should not be needed if we set
new_funcandnew_addrcorrectly inklp_init_patch_no_ops().
when were patching to the no_op yes the previous was what we want. However, if we are in the transition phase then the no_op means that we have the original function on the stack, not the new one?
Thanks for the review. As mentioned I will send out a v2 shortly.
Thanks,
-Jason
PATCH 3/3 livepatch: Add a sysctl livepatch_mode for atomic replace
In reply to Jason BaronIntroduce a sysctl knob such that by default livepatch is not in 'atomic replace' mode. A '0' in /proc/sys/kernel/livepatch_mode means the current default mode, while a '1' means do atomic replace.
--- include/linux/livepatch.h | 8 ++++++++ kernel/livepatch/core.c | 5 +++++ kernel/sysctl.c | 12 ++++++++++++ 3 files changed, 25 insertions(+)
include/linux/livepatch.h +8 -0
@@ -35,6 +35,14 @@#define KLP_UNPATCHED 0#define KLP_PATCHED 1 +/* livepatch mode */ + +extern int sysctl_livepatch_mode; +enum { +LIVEPATCH_MODE_DEFAULT , +LIVEPATCH_MODE_REPLACE , +}; +/** * struct klp_func - function structure for live patching * @old_name: name of the function to be patched
kernel/livepatch/core.c +5 -0
@@ -49,6 +49,8 @@ static LIST_HEAD(klp_patches);static struct kobject *klp_root_kobj; +int sysctl_livepatch_mode; +static bool klp_is_module (struct klp_object *obj) {return obj->name;
@@ -643,6 +645,9 @@ static int klp_init_patch_no_ops(struct klp_patch *patch)if (patch->list.prev == &klp_patches)return 0 ; +if (sysctl_livepatch_mode !=LIVEPATCH_MODE_REPLACE ) +return 0 ; + prev_patch =list_prev_entry (patch, list);klp_for_each_object (prev_patch, prev_obj, &prev_o_iter) {if (!klp_is_object_loaded (prev_obj))
kernel/sysctl.c +12 -0
@@ -67,6 +67,7 @@#include <linux/kexec.h> #include <linux/bpf.h> #include <linux/mount.h> +#include <linux/livepatch.h> #include <linux/uaccess.h> #include <asm/processor.h>
@@ -1203,6 +1204,17 @@ static struct ctl_table kern_table[] = { .extra2 = &one, },#endif +#ifdef CONFIG_LIVEPATCH + { + .procname = "livepatch_mode", + .data = &sysctl_livepatch_mode, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &zero, + .extra2 = &one, + }, +#endif { } };
Signature · 1 line
2.6.1
Miroslav Benes
maintainerQuoted 13 lines · Jason Baron
Hi,
In testing livepatch, I found that when doing cumulative patches, if a patched function is completed reverted by a subsequent patch (back to its original state) livepatch does not revert the funtion to its original state. Specifically, if patch A introduces a change to function 1, and patch B reverts the change to function 1 and introduces changes to say function 2 and 3 as well, the change that patch A introducd to function 1 is still present. This could be addressed by first completely removing patch A (disable and then rmmod) and then inserting patch B (insmod and enable), but this leaves an unpatched window. In discussing this issue with Josh on the kpatch mailing list, he mentioned that we could get 'atomic replace working properly', and that is the direction of this patchset:
https://www.redhat.com/archives/kpatch/2017-June/msg00005.html
Hi Jason,
this has been on my TODO list for a long time now, so thanks for working on this. We have the same feature in kGraft and we use it heavily (in fact we distribute our patches as cumulative and "replace_all" how we call it).
The forward port of the feature from kGraft is unfortunately not straightforward. We do not have a concept of klp_target_state there, so we can freely let functions to be patched or reverted in one go. We cannot do the same upstream. At first glance, you used nop function exactly for this case. Nice hack :).
Quoted 8 lines
Patches:
1) livepatch: Add
klp_objectandklp_funciterators Just a prep patch for the 'atomic revert' feature2) livepatch: add atomic replace
Core feature
Quoted 7 lines
3) livepatch: Add a sysctl
livepatch_modefor atomic replace Introduces a knob for enabling atomic replace. I hate knobs and perhaps its possible to default to cumulative replace? Although I suspect there are workflows relying on the existing behavior - I'm not sure. It may be desirable to associate the knob with the patch itself as in the 'immediate' flag, such that we don't introduce a global sysctl that likely would also need to built-in, if there are patches in the initrd.
Yes. I think it should be associated with the patch itself. This would allow more flexible behaviour. You could stack more patches on top of "atomic replace" patch.
Anyway, I'm on holiday next week, so I'll take a proper look the week after.
Thanks,
Miroslav
Quoted 16 lines · Miroslav Benes
Quoted 13 lines · Jason Baron
Hi,
In testing livepatch, I found that when doing cumulative patches, if a patched function is completed reverted by a subsequent patch (back to its original state) livepatch does not revert the funtion to its original state. Specifically, if patch A introduces a change to function 1, and patch B reverts the change to function 1 and introduces changes to say function 2 and 3 as well, the change that patch A introducd to function 1 is still present. This could be addressed by first completely removing patch A (disable and then rmmod) and then inserting patch B (insmod and enable), but this leaves an unpatched window. In discussing this issue with Josh on the kpatch mailing list, he mentioned that we could get 'atomic replace working properly', and that is the direction of this patchset:
https://www.redhat.com/archives/kpatch/2017-June/msg00005.html
Quoted 6 lines
Hi Jason,
this has been on my TODO list for a long time now, so thanks for working on this. We have the same feature in kGraft and we use it heavily (in fact we distribute our patches as cumulative and "
replace_all" how we call it).
Hi Miroslav,
Cool - we feel like this is an important feature as well and would like to have an upstream solution as well.
Quoted 22 lines
The forward port of the feature from kGraft is unfortunately not straightforward. We do not have a concept of
klp_target_statethere, so we can freely let functions to be patched or reverted in one go. We cannot do the same upstream. At first glance, you used nop function exactly for this case. Nice hack :).Quoted 8 lines
Patches:
1) livepatch: Add
klp_objectandklp_funciterators Just a prep patch for the 'atomic revert' feature2) livepatch: add atomic replace
Core featureQuoted 7 lines
3) livepatch: Add a sysctl
livepatch_modefor atomic replace Introduces a knob for enabling atomic replace. I hate knobs and perhaps its possible to default to cumulative replace? Although I suspect there are workflows relying on the existing behavior - I'm not sure. It may be desirable to associate the knob with the patch itself as in the 'immediate' flag, such that we don't introduce a global sysctl that likely would also need to built-in, if there are patches in the initrd.
Quoted 4 lines
Yes. I think it should be associated with the patch itself. This would allow more flexible behaviour. You could stack more patches on top of "atomic replace" patch.
Ok - associating the "atomic replace" with the patch itself makes sense to me. It would also basically work, I think with the patch I proposed except for the case where the the "atomic replace" was on top of several non-"atomic replace" patches. The reason is that the "atomic replace" I posted looks back 1 patch to see what it needs to replace (assuming all patches are in atomic replace mode). So instead of just looking back 1 patch, it could 'look back' and make sure it was replacing all previously loaded patches.
Quoted 3 lines
Anyway, I'm on holiday next week, so I'll take a proper look the week after.
Ok - have a nice holiday!
Thanks,
-Jason
Quoted 3 lines
Thanks,
Miroslav
Miroslav Benes
maintainerQuoted 8 lines
Ok - associating the "atomic replace" with the patch itself makes sense to me. It would also basically work, I think with the patch I proposed except for the case where the the "atomic replace" was on top of several non-"atomic replace" patches. The reason is that the "atomic replace" I posted looks back 1 patch to see what it needs to replace (assuming all patches are in atomic replace mode). So instead of just looking back 1 patch, it could 'look back' and make sure it was replacing all previously loaded patches.
Yes, this should not be a problem to implement with the current code.
But I must say I am not entirely happy with the code as it is. The big advantage is that there are almost no changes to the consistency model. Almost everything is created and allocated during the initialization and the patch then looks ordinary. The current code can handle it smoothly. "Dynamic" superstructure is what I am not happy with. It is too complicated in my opinion. On the other hand I don't see a simple way out now.
1. We can try to make your code "nicer".
2. The main problem is that all user structures (klp_func, klp_object, klp_patch) are statically allocated in a kernel module. You need to add dynamically allocated structures, which is not easy. We can either change everything to dynamic allocation, or make everything static. As far as the former is concerned, we've been down that road IIRC. It didn't end up well. Is the latter even possible?
3. We could bypass everything and register special no-op fops to each reverted function. Those could be held and handled separately. It could be simpler, but it might be problematic to deal with the consistency model interaction.
Thoughts?
Regards,
Miroslav
Quoted 11 lines · Miroslav Benes
Quoted 8 lines
Ok - associating the "atomic replace" with the patch itself makes sense to me. It would also basically work, I think with the patch I proposed except for the case where the the "atomic replace" was on top of several non-"atomic replace" patches. The reason is that the "atomic replace" I posted looks back 1 patch to see what it needs to replace (assuming all patches are in atomic replace mode). So instead of just looking back 1 patch, it could 'look back' and make sure it was replacing all previously loaded patches.
Yes, this should not be a problem to implement with the current code.
Quoted 7 lines
But I must say I am not entirely happy with the code as it is. The big advantage is that there are almost no changes to the consistency model. Almost everything is created and allocated during the initialization and the patch then looks ordinary. The current code can handle it smoothly. "Dynamic" superstructure is what I am not happy with. It is too complicated in my opinion. On the other hand I don't see a simple way out now.
Indeed. It is possible create the 'no-op' functions statically and relative to a previous livepatch module at build time, but I didn't like this approach because it means that the created 'no-op' functions are tied to a specific prior livepatch module, which it is potentially also inconvenient to keep around. So for me, the dynamic creation of the 'no-op' functions is important.
Given that premise, the posted patch adds an additional linked list head to klp_patch (to add the dynamic klp_objects), and an additional linked list head to the klp_object (to add the dynamic klp_funcs). It hides this mostly behind the klp_for_each_object() and klp_for_each_func(), such that a lot of the code does not need to be adjusted. Note as well that I think in general the lists of 'no-ops' should be fairly small, and that as soon as the live patch module is enabled the linked lists are removed and thus the livepatch module looks the same as before (only contains the static allocations).
Quoted 3 lines
1. We can try to make your code "nicer".
Sure - I'm happy to incorporate any suggestions, and I could re-review to try and simplify further.
Quoted 7 lines
2. The main problem is that all user structures (
klp_func,klp_object,klp_patch) are statically allocated in a kernel module. You need to add dynamically allocated structures, which is not easy. We can either change everything to dynamic allocation, or make everything static. As far as the former is concerned, we've been down that road IIRC. It didn't end up well. Is the latter even possible?
I think making everything static is possible - but I think it ties things to the previously loaded module, which I don't think is desirable. That said we could also potentially reallocate() the memory for the static regions and increase them to the required size (although it may be a little tricky to free the original static region b/c other data structures may be stored nearby?). I think its also nice that the current patch frees these 'no-op' functions when no longer needed, so by keeping them separate, this is somewhat simpler.
Quoted 5 lines
3. We could bypass everything and register special no-op fops to each reverted function. Those could be held and handled separately. It could be simpler, but it might be problematic to deal with the consistency model interaction.
Perhaps...I sort of feel like this moves us closer to handling these differently, whereas the proposal hides a lot of this behind klp_for_each_object() and klp_for_each_func().
Thanks,
-Jason
Miroslav Benes
maintainerQuoted 28 lines · Jason Baron
Quoted 17 lines · Miroslav Benes
Quoted 14 lines
Ok - associating the "atomic replace" with the patch itself makes sense to me.
It would also basically work, I think with the patch I proposed except for the
case where the the "atomic replace" was on top of several non-"atomic replace"
patches. The reason is that the "atomic replace" I posted looks back 1 patch
to see what it needs to replace (assuming all patches are in atomic replace
mode). So instead of just looking back 1 patch, it could 'look back' and make
sure it was replacing all previously loaded patches.Yes, this should not be a problem to implement with the current code.
Quoted 7 lines
But I must say I am not entirely happy with the code as it is. The big advantage is that there are almost no changes to the consistency model. Almost everything is created and allocated during the initialization and the patch then looks ordinary. The current code can handle it smoothly. "Dynamic" superstructure is what I am not happy with. It is too complicated in my opinion. On the other hand I don't see a simple way out now.
Quoted 5 lines
Indeed. It is possible create the 'no-op' functions statically and relative to a previous livepatch module at build time, but I didn't like this approach because it means that the created 'no-op' functions are tied to a specific prior livepatch module, which it is potentially also inconvenient to keep around. So for me, the dynamic creation of the 'no-op' functions is important.
Yes, I agree. I think the feature should be transparent for the user (livepatch author). Otherwise, its benefits are questionable.
Quoted 25 lines
Given that premise, the posted patch adds an additional linked list head to
klp_patch(to add the dynamicklp_objects), and an additional linked list head to theklp_object(to add the dynamicklp_funcs). It hides this mostly behind theklp_for_each_object()andklp_for_each_func(), such that a lot of the code does not need to be adjusted. Note as well that I think in general the lists of 'no-ops' should be fairly small, and that as soon as the live patch module is enabled the linked lists are removed and thus the livepatch module looks the same as before (only contains the static allocations).Quoted 3 lines
1. We can try to make your code "nicer".
Sure - I'm happy to incorporate any suggestions, and I could re-review to try and simplify further.
Quoted 7 lines
2. The main problem is that all user structures (
klp_func,klp_object,klp_patch) are statically allocated in a kernel module. You need to add dynamically allocated structures, which is not easy. We can either change everything to dynamic allocation, or make everything static. As far as the former is concerned, we've been down that road IIRC. It didn't end up well. Is the latter even possible?
Quoted 2 lines
I think making everything static is possible - but I think it ties things to the previously loaded module, which I don't think is desirable.
Agreed.
Quoted 4 lines
That said we
could also potentiallyreallocate()the memory for the static regions and increase them to the required size (although it may be a little tricky to free the original static region b/c other data structures may be stored nearby?).
Yes, that is what I meant above and I am not sure we can/should do it.
Quoted 3 lines
I
think its also nice that the current patch frees these 'no-op' functions when no longer needed, so by keeping them separate, this is somewhat simpler.
True.
Quoted 9 lines
Quoted 5 lines
3. We could bypass everything and register special no-op fops to each reverted function. Those could be held and handled separately. It could be simpler, but it might be problematic to deal with the consistency model interaction.
Perhaps...I sort of feel like this moves us closer to handling these differently, whereas the proposal hides a lot of this behind
klp_for_each_object()andklp_for_each_func().
Ok, I'll take the second look and maybe my impression would be different. I'll try to come up with something.
Miroslav