Re: [PATCH v2 2/2] livepatch: Fix UAF of unregistered patch kobjects

Petr Mladek <[email protected]>
Newsgroups org.kernel.vger.live-patching
Message-ID <[email protected]>
On Wed 2026-08-19 20:37:17, Yafang Shao wrote:
> On Wed, Aug 19, 2026 at 7:18 PM Petr Mladek <[email protected]> wrote:
> >
> > On Sun 2026-08-16 17:04:42, Yafang Shao wrote:
> > > When klp_enable_patch() fails after klp_init_patch_early() has run,
> > > the error path calls klp_free_patch_start() and klp_free_patch_finish().
> > > The former drops the references of all object and function kobjects via
> > > klp_free_objects(), the latter drops the patch kobject reference and
> > > waits for the patch kobject release only:
> > >
> > >     klp_free_patch_finish():
> > >         kobject_put(&patch->kobj);
> > >         wait_for_completion(&patch->finish);
> > >
> > > With CONFIG_DEBUG_KOBJECT_RELEASE enabled, kobject_put() does not
> > > release the kobject synchronously but schedules a delayed release with
> > > a random delay of up to 4 seconds (see kobject_release() in
> > > lib/kobject.c).
> >
> > Yes.
> >
> > > Because klp_free_patch_finish() only waits for the
> > > patch kobject release, it may return while object and function kobject
> > > releases are still pending. The caller can then unload the livepatch
> > > module, which frees the klp_object and klp_func structures. The delayed
> > > kobject release callbacks later access this freed memory in
> > > kobject_cleanup(), resulting in a use-after-free.
> > >
> > > This issue can occur in two scenarios:
> > >
> > > 1. The patch kobject was never added to sysfs (e.g., klp_init_patch()
> > >    failed at kobject_add()). All child kobjects were only initialized
> > >    via kobject_init() but never added to sysfs. They do not hold
> > >    references to the patch kobject, so the patch kobject can be
> > >    released independently, unblocking patch->finish before the child
> > >    releases complete.
> > >
> > > 2. The patch kobject was added to sysfs, but a subsequent operation
> > >    such as klp_add_nops() or klp_init_object() failed. Some child
> > >    kobjects were initialized but not yet added to sysfs. These
> > >    un-added children do not hold references to the patch kobject
> > >    either, so the same race can occur.
> >
> > In short, this says that the races might happen when some kobjects
> > were not added into sysfs. Am I right, please?
> 
> right
> 
> >
> > I agree. My undestading:
> >
> > The klp_kobj_release_*() callbacks are called by kobject_cleanup()
> > which calls kobject_put(parent) as the last step. It should make sure
> > that:
> >
> >   + klp_kobj_release_patch() is scheduled/called only when
> >     klp_kobj_release_object() has been called for all patch->objs.
> >
> >   + klp_kobj_release_object() is scheduled/called only when
> >     klp_kobj_release_func() has been called for all obj->funcs.
> >
> > But it works only when "kobj->parent" is set and
> > "parent->kref" has been incremented for each child before.
> >
> > This is true only when kobject_add() is called for all all used
> > kobjects. But it is not guaranteed when any klp_init_*() failed.
> 
> correct
> 
> >
> > > Fix this by tracking all static kobject releases with a per-patch
> > > atomic counter (kobj_pending). klp_free_patch_start() counts the
> > > patch kobject plus all static object and function kobjects.
> > > klp_free_patch_finish() waits until kobj_pending reaches zero,
> > > ensuring all kobject releases have completed before the module is
> > > unloaded.
> >
> > I think that we do not need an extra couter. We might use
> > the existing kobj->kref. We just need to explicitely
> > increment/decrement it.
> 
> good idea
> 
> >
> > I mean something like:
> >
> > diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> > index 28d15ba58a26..023f666ddcc4 100644
> > --- a/kernel/livepatch/core.c
> > +++ b/kernel/livepatch/core.c
> > @@ -652,6 +652,8 @@ static void klp_kobj_release_object(struct kobject *kobj)
> >
> >         if (obj->dynamic)
> >                 klp_free_object_dynamic(obj);
> > +       else
> > +               kobject_put(&obj->patch.kobj);
> >  }
> 
> It appears that klp_init_object_early() also initializes non-dynamic
> objects, right?

I was a bit confused by the sentence. The dynamic objects
do not exist when klp_init_object_early() is called in
klp_init_patch_early().

But I see that it is called also in klp_alloc_object_dynamic().

> Therefore, we should call kobject_put() unconditionally.

Great catch. Yes, we should call it unconditionally.

>  static void klp_kobj_release_object(struct kobject *kobj)
>  {
>         struct klp_object *obj;
> +       struct klp_patch *patch;
> 
>         obj = container_of(kobj, struct klp_object, kobj);
> +       patch = obj->patch;
> 
>         if (obj->dynamic)
>                 klp_free_object_dynamic(obj);
> +
> +       kobject_put(&patch->kobj);
>  }

Looks good. Same with klp_kobj_release_func().

Best Regards,
Petr
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.