Re: [PATCH v2 2/3] livepatch: add atomic replace

From: Jason Baron
Date: Thu Sep 14 2017 - 11:32:04 EST




On 09/14/2017 06:32 AM, Petr Mladek wrote:
> On Tue 2017-09-12 23:47:32, Jason Baron wrote:
>>
>>
>> On 09/12/2017 01:35 AM, Petr Mladek wrote:
>>> On Mon 2017-09-11 23:46:28, Jason Baron wrote:
>>>> On 09/11/2017 09:53 AM, Petr Mladek wrote:
>>>>> On Wed 2017-08-30 17:38:44, Jason Baron wrote:
>>>>>> diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
>>>>>> index 8d3df55..ee6d18b 100644
>>>>>> --- a/include/linux/livepatch.h
>>>>>> +++ b/include/linux/livepatch.h
>>>>>> @@ -119,10 +121,12 @@ struct klp_object {
>>>>>> * @mod: reference to the live patch module
>>>>>> * @objs: object entries for kernel objects to be patched
>>>>>> * @immediate: patch all funcs immediately, bypassing safety mechanisms
>>>>>> + * @replace: replace all funcs, reverting functions that are no longer patched
>>>>>> * @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)
>>>>>> + * @replaced: the patch has been replaced an can not be re-enabled
>>>>>
>>>>> I finally understand why you do this. I forgot that even disabled
>>>>> patches are still in klp_patch list.
>>>>>
>>>>> This makes some sense for patches that fix different bugs and
>>>>> touch the same function. They should be applied and enabled
>>>>> in the right order because a later patch for the same function
>>>>> must be based on the code from the previous one.
>>>>>
>>>>> It makes less sense for cummulative patches that we use in kGraft.
>>>>> There basically every patch has the "replace" flag set. If
>>>>> we enable one patch it simply replaces any other one. Then
>>>>> ordering is not important. Each patch is standalone and
>>>>> consistent on its own.
>>>>>
>>>>>
>>>>> I see two problems with your approach. One is cosmetic.
>>>>> The names of the two flags replace/replaced are too similar.
>>>>> It is quite prone for mistakes ;-)
>>>>>
>>>>> Second, we could not remove module where any function is patched
>>>>> usign the "immediate" flag. But people might want to revert
>>>>> to this patch if the last one does not work as expected.
>>>>> After all the main purpose of livepatching is to fix
>>>>> problems without a system reboot. Therefore we should
>>>>> allow to enable the replaced patches even without
>>>>> removing the module.
>>>>>
>>>>
>>>> If the livepatch has the 'replace' bit set and not the 'immediate' bit,
>>>> then I believe that we could remove *all* types of previous livepatches
>>>> even if they have the 'immediate' flag set. That is, because of the
>>>> consistency model, we are sure that we are running only functions from
>>>> the cumulative replace patch.
>>>
>>> You are partly right. The catch is if a function was previously
>>> patched using the immediate flag and the function is not longer
>>> patched by the new cumulative patch with the 'replace' flag.
>>> Then we need to create "no_op" for the function and the 'no_op'
>>> must inherit the immediate flag set. Then the consistency model
>>> does not guarantee that the code from the older patch is not running
>>> and we could not allow to remove the older patch.
>>>
>>
>> Agreed - the replace patch should 'inherit' the immediate flag. This
>> raises the question, what if say two patches have the immediate flag set
>> differently for the same function? This would be unlikely since
>> presumably we would set it the same way for all patches. In this case, I
>> think a reasonable thing to do would be to 'inherit' the immediate flag
>> from the immediately proceeding patch, since that's the one we are
>> reverting from.
>
> Good question. I would personally use immediate = false if there
> is a mismatch. It might block the transition but it will not break
> the consistency model. The consistency and stability is always
> more important. The user could always either revert the transition.
> Or there is going to be a way to force it.
>
> IMHO, there are basically two approaches how to use
> the immediate flag:
>
> Some people might enable it only when it is safe and needed
> to patch a function that might sleep for long. These people
> either want to be on the safe side. Or they want to remove
> disabled patches when possible. Your approach would be fine
> in this case.
>
> Another group of people might always enable the immediate flag
> when it is safe. They would disable the immediate flag only when
> the patch does a semantic change. These people want to keep
> it easy and avoid potential troubles with a transition
> (complex code, might get blocked, ...). This is the reason
> to be conservative. The cumulative patch is going to replace
> all existing patches. If one of them needed the complex
> consistency model, we should use it as well.
>

Ok, makes sense.

>
>>>>>> diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
>>>>>> index 6004be3..21cecc5 100644
>>>>>> --- a/kernel/livepatch/core.c
>>>>>> +++ b/kernel/livepatch/core.c
>>>>>> @@ -600,13 +603,38 @@ static void klp_free_patch(struct klp_patch *patch)
>>>>>> list_del(&patch->list);
>>>>>> }
>>>>>>
>>>>>> -static int klp_init_func(struct klp_object *obj, struct klp_func *func)
>>>>>> +void klp_patch_free_no_ops(struct klp_patch *patch)
>>>>>> +{
>>>>>> + struct klp_object *obj, *tmp_obj;
>>>>>> + struct klp_func *func, *tmp_func;
>>>>>> +
>>>>>> + klp_for_each_object(patch, obj) {
>>>>>> + list_for_each_entry_safe(func, tmp_func, &obj->func_list,
>>>>>> + func_entry) {
>>>>>> + list_del_init(&func->func_entry);
>>>>>> + kobject_put(&func->kobj);
>>>>>> + kfree(func->old_name);
>>>>>> + kfree(func);
>>>>>
>>>>> kobject_put() is asynchronous. The structure should be freed using
>>>>> the kobject release method.
>>>>>
>>>>> The question is how secure this should be. We might want to block
>>>>> other operations with the patch until all the release methods
>>>>> are called. But this might be tricky as there is not a single
>>>>> root kobject that would get destroyed at the end.
>>>>>
>>>>> A crazy solution would be to define a global atomic counter.
>>>>> It might get increased with each kobject_put() call and
>>>>> descreased in each release method. Then we could wait
>>>>> until it gets zero.
>>>>>
>>>>> It should be safe to wait with klp_mutex taken. Note that this
>>>>> is not possible with patch->kobj() where the "the enable"
>>>>> attribute takes the mutex as well, see
>>>>> enabled_store() in kernel/livepatch/core.c.
>>>>
>>>> Thanks for pointing this out - it looks like the livepatch code uses
>>>> wait_for_completion() with special kobj callbacks. Perhaps, there could
>>>> be a nop callback, but I'd have to look at this more closely...
>>>
>>> The completion is usable for the root kobject but you do not free
>>> it here. It might be pretty complicated if you need separate
>>> completion for all the freed kobjects.
>>>
>>> A solution might be a global atomic counter and a waitqueue.
>>> Feel free to ask for more details.
>>>
>>
>> So the issue is that the user might access some of the klp_* data
>> structures via /sysfs after we have already freed them?
>
> yes
>
>> if so, this seems to be a common kernel pattern:
>>
>> kobject_del(kobj);
>> kobject_put(kobj);
>
> IMHO, this is used for other reason.
>
> kobject_del() removes the object from the hierachy. Therefore
> it prevents new operations. But it does not wait for the exiting
> operations to finish. Therefore there still might be users that
> access the data even after this function finishes.

I looked into this further - and it does appear to wait until all
operations finish. In kernfs_drain() the code does:

wait_event(root->deactivate_waitq, atomic_read(&kn->active) ==
KN_DEACTIVATED_BIAS);

The call stack is:

kobject_del()
sysfs_remove_dir()
kernfs_remove()
__kernfs_remove()
kernfs_drain()

And __kernfs_remove() has already done a 'deactivate' prior:

/* prevent any new usage under @kn by deactivating all nodes */

pos = NULL;

while ((pos = kernfs_next_descendant_post(pos, kn)))

if (kernfs_active(pos))

atomic_add(KN_DEACTIVATED_BIAS, &pos->active);


So I *think* doing the kobject_del() first is sufficient. It may be
worth some better documented if that is the case...


Thanks,

-Jason


>
> kobject_put() is enough if you do not mind how the object
> might be visible. It would remove it once the last reference
> on the object is removed. See the following code in
> kobject_cleanup.
>
> /* remove from sysfs if the caller did not do it */
> if (kobj->state_in_sysfs) {
> pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n",
> kobject_name(kobj), kobj);
> kobject_del(kobj);
> }
>
> In each case, you must not free the data accessible from the kobject
> handlers until kobj_type->release method is called.
>
>
> IMHO, the solution is not that complicated after all.
> If you are able to distinguish statically and dynamically
> defined klp_func and klp_obj structures, you might
> just modify the existing kobject release methods:
>
>
> atomic_t klp_no_op_release_count;
> static DECLARE_WAIT_QUEUE_HEAD(klp_no_op_release_wait);
>
> static void klp_kobj_release_object(struct kobject *kobj)
> {
> struct klp_object *obj;
>
> obj = container_of(kobj, struct klp_object, kobj);
> /* Free dynamically allocated object */
> if (!obj->funcs) {
> kfree(obj->name);
> kfree(obj);
> atomic_dec(&klp_no_op_release_count);
> wake_up(&klp_no_op_release_wait);
> }
> }
>
> static void klp_kobj_release_func(struct kobject *kobj)
> {
> struct klp_func *func;
>
> obj = container_of(kobj, struct klp_func, kobj);
> /* Free dynamically allocated functions */
> if (!func->new_func) {
> kfree(func->old_name);
> kfree(func);
> atomic_dec(&klp_no_op_release_count);
> wake_up(&klp_no_op_release_wait);
> }
> }
>
>
> then we could have
>
> void klp_patch_free_no_ops(struct klp_patch *patch)
> {
> struct klp_object *obj, *tmp_obj;
> struct klp_func *func, *tmp_func;
>
> klp_for_each_object(patch, obj) {
> list_for_each_entry_safe(func, tmp_func, &obj->func_list,
> func_entry) {
> list_del_init(&func->func_entry);
> atomic_inc(&klp_no_op_release_count);
> kobject_put(&func->kobj);
> }
> INIT_LIST_HEAD(&obj->func_list);
> }
> list_for_each_entry_safe(obj, tmp_obj, &patch->obj_list, obj_entry) {
> list_del_init(&obj->obj_entry);
> atomic_inc(&klp_no_op_release_count);
> kobject_put(&obj->kobj);
> }
> INIT_LIST_HEAD(&patch->obj_list);
>
> wait_event(&klp_no_op_release_wait,
> atomic_read(&klp_no_op_release_count) == 0);
> }
>
> Add we should call this function also in klp_complete_transition()
> to avoid code duplication.
>
>
>>>>>> + }
>>>>>> + INIT_LIST_HEAD(&obj->func_list);
>>>>>> + }
>>>>>> + list_for_each_entry_safe(obj, tmp_obj, &patch->obj_list, obj_entry) {
>>>>>> + list_del_init(&obj->obj_entry);
>>>>>> + kobject_put(&obj->kobj);
>>>>>> + kfree(obj->name);
>>>>>> + kfree(obj);
>>>>>
>>>>> Same here.
>>>>>
>>>>>> + }
>>>>>> + INIT_LIST_HEAD(&patch->obj_list);
>>>>>> +}
>>>>>> +
>>>
>>>
>>>>>> diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c
>>>>>> index b004a1f..d5e620e 100644
>>>>>> --- a/kernel/livepatch/transition.c
>>>>>> +++ b/kernel/livepatch/transition.c
>>>
>>>>>
>>>>>> + }
>>>>>> + }
>>>>>> + klp_unpatch_objects(klp_transition_patch, true);
>>>>>> + no_op = true;
>>>>>> + }
>>>>>>
>>>>>> if (klp_target_state == KLP_UNPATCHED) {
>>>>>> /*
>>>>>> * All tasks have transitioned to KLP_UNPATCHED so we can now
>>>>>> * remove the new functions from the func_stack.
>>>>>> */
>>>>>> - klp_unpatch_objects(klp_transition_patch);
>>>>>> + klp_unpatch_objects(klp_transition_patch, false);
>>>>>
>>>>> This code patch made me think about a situation when
>>>>> the "enable" operation was reverted during the transition.
>>>>> Then the target state is KLP_UNPATCHED and no_op stuff
>>>>> is there.
>>>>>
>>>>> Well, in this case, we need to keep the no_op stuff
>>>>> until either the the patch is enabled egain and the
>>>>> transition finishes or we need to remove/free it
>>>>> klp_unregister_patch().
>>>>
>>>> In the case that klp_cancel_transition() calls klp_complete_transition()
>>>> and we remove the nop functions, we also remove all the regular
>>>> 'functions', which should be re-added on the function stacks if the
>>>> patch is re-enabled. So I think this is fine.
>>>
>>> But we do not remove no_op when klp_target_state == KLP_PATCHED.
>>> And this might never happen when we call klp_reverse_transition()
>>> in the middle of the transition.
>>>
>>> I see two solutions:
>>>
>>> Either we could postpone generating the no_op functions
>>> until __klp_enable_patch() call and always free
>>> them in klp_finish_transition().
>>>
>>> Or we need to check if some no_op functions are still
>>> there when __klp_unregister_patch() is called and free
>>> them there.
>>>
>>>
>>> In the long term, we would need the first solution
>>> because it would allow to re-enable the replaced
>>> patches. But it will be more complicated to reuse
>>> the existing code, especially the init functions.
>>>
>>> Feel free to keep it easy and implement
>>> the 2nd possibility in this patch(set).
>>>
>>
>> Indeed I took the 2nd possibility in this patch already - see the call
>> to klp_patch_free_no_ops() in klp_unregister_patch().
>
> Ah, I have missed this. Then we are on the safe side.
>
> Best Regards,
> Petr
>