On Tue, Jun 02, 2020 at 09:49:10PM -0700, Cong Wang wrote:
> diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
> index 9f357aa22b94..bcbba0bef1c2 100644
> --- a/net/netlink/genetlink.c
> +++ b/net/netlink/genetlink.c
> @@ -513,15 +513,58 @@ static void genl_family_rcv_msg_attrs_free(const struct 
> genl_family *family,
>               kfree(attrbuf);
>  }
>  
> -static int genl_lock_start(struct netlink_callback *cb)
> +struct genl_start_context {
> +     const struct genl_family *family;
> +     struct nlmsghdr *nlh;
> +     struct netlink_ext_ack *extack;
> +     const struct genl_ops *ops;
> +     int hdrlen;
> +};
> +
> +static int genl_start(struct netlink_callback *cb)
>  {
> -     const struct genl_ops *ops = genl_dumpit_info(cb)->ops;
> +     struct genl_start_context *ctx = cb->data;
> +     const struct genl_ops *ops = ctx->ops;
> +     struct genl_dumpit_info *info;
> +     struct nlattr **attrs = NULL;
>       int rc = 0;
>  
> +     if (ops->validate & GENL_DONT_VALIDATE_DUMP)
> +             goto no_attrs;
> +
> +     if (ctx->nlh->nlmsg_len < nlmsg_msg_size(ctx->hdrlen))
> +             return -EINVAL;
> +
> +     attrs = genl_family_rcv_msg_attrs_parse(ctx->family, ctx->nlh, 
> ctx->extack,
> +                                             ops, ctx->hdrlen,
> +                                             GENL_DONT_VALIDATE_DUMP_STRICT,
> +                                             true);
> +     if (IS_ERR(attrs))
> +             return PTR_ERR(attrs);
> +
> +no_attrs:
> +     info = genl_dumpit_info_alloc();
> +     if (!info) {
> +             kfree(attrs);
> +             return -ENOMEM;
> +     }
> +     info->family = ctx->family;
> +     info->ops = ops;
> +     info->attrs = attrs;
> +
> +     cb->data = info;
>       if (ops->start) {
> -             genl_lock();
> +             if (!ctx->family->parallel_ops)
> +                     genl_lock();
>               rc = ops->start(cb);
> -             genl_unlock();
> +             if (!ctx->family->parallel_ops)
> +                     genl_unlock();
> +     }
> +
> +     if (rc) {
> +             kfree(attrs);
> +             genl_dumpit_info_free(info);
> +             cb->data = NULL;
>       }
>       return rc;
>  }
> @@ -548,7 +591,7 @@ static int genl_lock_done(struct netlink_callback *cb)
>               rc = ops->done(cb);
>               genl_unlock();
>       }
> -     genl_family_rcv_msg_attrs_free(info->family, info->attrs, true);
> +     genl_family_rcv_msg_attrs_free(info->family, info->attrs, false);

Cong,

This seems to result in a memory leak because 'info->attrs' is never
freed in the non-parallel case.

Both the parallel and non-parallel code paths call genl_start() which
allocates the array, but the latter calls genl_lock_done() as its done()
callback which never frees it.

Can be reproduced as follows:

echo "10 1" > /sys/bus/netdevsim/new_device
devlink trap &> /dev/null
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak

unreferenced object 0xffff88810f1ed000 (size 2048):
  comm "devlink", pid 201, jiffies 4295606431 (age 35.858s)
  hex dump (first 32 bytes):
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  backtrace:
    [<00000000a7cb7530>] __kmalloc+0x1d6/0x3d0
    [<000000001cb013e1>] genl_family_rcv_msg_attrs_parse+0x1f3/0x320
    [<00000000b201bc93>] genl_start+0x1ab/0x5e0
    [<00000000786e531e>] __netlink_dump_start+0x5b5/0x940
    [<00000000a2332fcb>] genl_family_rcv_msg_dumpit+0x32e/0x3a0
    [<00000000112052dd>] genl_rcv_msg+0x6d7/0xb40
    [<000000005826e358>] netlink_rcv_skb+0x175/0x490
    [<000000002c5f41ae>] genl_rcv+0x2d/0x40
    [<00000000f0301e6d>] netlink_unicast+0x5d0/0x7f0
    [<00000000a76a3934>] netlink_sendmsg+0x981/0xe90
    [<000000001c478a6f>] __sys_sendto+0x2cd/0x450
    [<0000000079d420b0>] __x64_sys_sendto+0xe6/0x1a0
    [<000000004e535e4b>] do_syscall_64+0xc1/0x600
    [<000000006e5dd3c4>] entry_SYSCALL_64_after_hwframe+0x49/0xb3

>       genl_dumpit_info_free(info);
>       return rc;
>  }
> @@ -573,43 +616,23 @@ static int genl_family_rcv_msg_dumpit(const struct 
> genl_family *family,
>                                     const struct genl_ops *ops,
>                                     int hdrlen, struct net *net)
>  {
> -     struct genl_dumpit_info *info;
> -     struct nlattr **attrs = NULL;
> +     struct genl_start_context ctx;
>       int err;
>  
>       if (!ops->dumpit)
>               return -EOPNOTSUPP;
>  
> -     if (ops->validate & GENL_DONT_VALIDATE_DUMP)
> -             goto no_attrs;
> -
> -     if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
> -             return -EINVAL;
> -
> -     attrs = genl_family_rcv_msg_attrs_parse(family, nlh, extack,
> -                                             ops, hdrlen,
> -                                             GENL_DONT_VALIDATE_DUMP_STRICT,
> -                                             true);
> -     if (IS_ERR(attrs))
> -             return PTR_ERR(attrs);
> -
> -no_attrs:
> -     /* Allocate dumpit info. It is going to be freed by done() callback. */
> -     info = genl_dumpit_info_alloc();
> -     if (!info) {
> -             genl_family_rcv_msg_attrs_free(family, attrs, true);
> -             return -ENOMEM;
> -     }
> -
> -     info->family = family;
> -     info->ops = ops;
> -     info->attrs = attrs;
> +     ctx.family = family;
> +     ctx.nlh = nlh;
> +     ctx.extack = extack;
> +     ctx.ops = ops;
> +     ctx.hdrlen = hdrlen;
>  
>       if (!family->parallel_ops) {
>               struct netlink_dump_control c = {
>                       .module = family->module,
> -                     .data = info,
> -                     .start = genl_lock_start,
> +                     .data = &ctx,
> +                     .start = genl_start,
>                       .dump = genl_lock_dumpit,
>                       .done = genl_lock_done,
>               };
> @@ -617,12 +640,11 @@ static int genl_family_rcv_msg_dumpit(const struct 
> genl_family *family,
>               genl_unlock();
>               err = __netlink_dump_start(net->genl_sock, skb, nlh, &c);
>               genl_lock();
> -
>       } else {
>               struct netlink_dump_control c = {
>                       .module = family->module,
> -                     .data = info,
> -                     .start = ops->start,
> +                     .data = &ctx,
> +                     .start = genl_start,
>                       .dump = ops->dumpit,
>                       .done = genl_parallel_done,
>               };
> -- 
> 2.26.2
> 

Reply via email to