[PATCH] ALSA: control led: fix memory leak in snd_ctl_led_register

Dongliang Mu mudongliangabcd at gmail.com
Tue Jun 1 15:17:04 CEST 2021


On Mon, May 31, 2021 at 7:02 PM Dan Carpenter <dan.carpenter at oracle.com> wrote:
>
> On Fri, May 28, 2021 at 09:17:57PM +0800, Dongliang Mu wrote:
> > The snd_ctl_led_sysfs_add and snd_ctl_led_sysfs_remove should contain
> > the refcount operations in pair. However, snd_ctl_led_sysfs_remove fails
> > to decrease the refcount to zero, which causes device_release never to
> > be invoked. This leads to memory leak to some resources, like struct
> > device_private.
> >
> > Fix this by calling put_device at the end of snd_ctl_led_sysfs_remove
> >
> > Reported-by: syzbot+08a7d8b51ea048a74ffb at syzkaller.appspotmail.com
> > Fixes: a135dfb5de1 ("ALSA: led control - add sysfs kcontrol LED marking layer")
> > Signed-off-by: Dongliang Mu <mudongliangabcd at gmail.com>
> > ---
> >  sound/core/control_led.c | 6 ++++++
> >  1 file changed, 6 insertions(+)
> >
> > diff --git a/sound/core/control_led.c b/sound/core/control_led.c
> > index 25f57c14f294..fff2688b5019 100644
> > --- a/sound/core/control_led.c
> > +++ b/sound/core/control_led.c
> > @@ -371,6 +371,10 @@ static void snd_ctl_led_disconnect(struct snd_card *card)
> >       snd_ctl_led_refresh();
> >  }
> >
> > +static void snd_ctl_led_release(struct device *dev)
> > +{
> > +}
>
> Just to clarify again some more, this call back has to free "led_card".
> This patch changes the memory leak into a use after free bug. (A use
> after free bug is worse than a memory leak).

Hi Dan,

I have read the whole thread several times. I don't quite understand
why you think this call back needs to free "led_card". In current
implementation, the led_card is allocated in snd_ctl_led_sysfs_add,
and released in snd_ctl_led_sysfs_remove. It seems there is no logic
issue. If we keep a dump function here, I think there should no UAF.

I agree with you. We shall be very careful about any added release
function. It might turn a memory leak into double-free or
use-after-free.

>
> There were some other leaks as discussed where a dummy free function is
> fine because they were dealing with static data structures (not
> allocated memory).
>
> > +
> >  /*
> >   * sysfs
> >   */
> > @@ -663,6 +667,7 @@ static void snd_ctl_led_sysfs_add(struct snd_card *card)
> >               led_card->number = card->number;
> >               led_card->led = led;
> >               device_initialize(&led_card->dev);
> > +             led_card->dev.release = snd_ctl_led_release;
> >               if (dev_set_name(&led_card->dev, "card%d", card->number) < 0)
> >                       goto cerr;
> >               led_card->dev.parent = &led->dev;
> > @@ -701,6 +706,7 @@ static void snd_ctl_led_sysfs_remove(struct snd_card *card)
> >               sysfs_remove_link(&card->ctl_dev.kobj, link_name);
> >               sysfs_remove_link(&led_card->dev.kobj, "card");
> >               device_del(&led_card->dev);
> > +             put_device(&led_card->dev);
> >               kfree(led_card);
> >               led->cards[card->number] = NULL;
> >       }
>
> Btw, I have created a Smatch warning for this type of code where we
> have:
>
>         put_device(&foo->dev);
>         kfree(foo);

I don't think this should be a bug pattern. put_device will drop the
final reference of one object with struct device and invoke
device_release to release some resources.

The release function should only clean up the internal resources in
the device object. It should not touch the led_card which contains the
device object.

>
> sound/core/control_led.c:709 snd_ctl_led_sysfs_remove() warn: freeing device managed memory: 'led_card'
>
> So hopefully that will prevent future similar bugs.  I'll test it out
> overnight and report back tomorrow how it works.
>
> regards,
> dan carpenter
>
> /*
>  * Copyright (C) 2021 Oracle.
>  *
>  * This program is free software; you can redistribute it and/or
>  * modify it under the terms of the GNU General Public License
>  * as published by the Free Software Foundation; either version 2
>  * of the License, or (at your option) any later version.
>  *
>  * This program is distributed in the hope that it will be useful,
>  * but WITHOUT ANY WARRANTY; without even the implied warranty of
>  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>  * GNU General Public License for more details.
>  *
>  * You should have received a copy of the GNU General Public License
>  * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
>  */
>
> #include "smatch.h"
>
> static int my_id;
>
> STATE(managed);
>
> static void set_ignore(struct sm_state *sm, struct expression *mod_expr)
> {
>         set_state(my_id, sm->name, sm->sym, &undefined);
> }
>
> static void match_put_device(const char *fn, struct expression *expr, void *param)
> {
>         struct expression *arg;
>
>         arg = get_argument_from_call_expr(expr->args, PTR_INT(param));
>         arg = strip_expr(arg);
>         if (!arg || arg->type != EXPR_PREOP || arg->op != '&')
>                 return;
>         arg = strip_expr(arg->unop);
>         if (!arg || arg->type != EXPR_DEREF)
>                 return;
>         arg = strip_expr(arg->deref);
>         if (arg && arg->type == EXPR_PREOP && arg->op == '*')
>                 arg = arg->unop;
>         set_state_expr(my_id, arg, &managed);
> }
>
> static void match_free(const char *fn, struct expression *expr, void *param)
> {
>         struct expression *arg;
>         char *name;
>
>         arg = get_argument_from_call_expr(expr->args, PTR_INT(param));
>         if (get_state_expr(my_id, arg) != &managed)
>                 return;
>         name = expr_to_str(arg);
>         sm_warning("freeing device managed memory: '%s'", name);
>         free_string(name);
> }
>
> void check_put_device(int id)
> {
>         my_id = id;
>
>         if (option_project != PROJ_KERNEL)
>                 return;
>
>         add_function_hook("put_device", &match_put_device, INT_PTR(0));
>         add_function_hook("device_unregister", &match_put_device, INT_PTR(0));
>
>         add_function_hook("kfree", &match_free, INT_PTR(0));
>         add_modification_hook(my_id, &set_ignore);
> }


More information about the Alsa-devel mailing list