Commit ff74745e authored by Andrew Gabbasov's avatar Andrew Gabbasov Committed by Felipe Balbi

usb: gadget: configfs: Fix memory leak of interface directory data

Kmemleak checking configuration reports a memory leak in
usb_os_desc_prepare_interf_dir function when rndis function
instance is freed and then allocated again. For example, this
happens with FunctionFS driver with RNDIS function enabled
when "ffs-test" test application is run several times in a row.

The data for intermediate "os_desc" group for interface directories
is allocated as a single VLA chunk and (after a change of default
groups handling) is not ever freed and actually not stored anywhere
besides inside a list of default groups of a parent group.

The fix is to make usb_os_desc_prepare_interf_dir function return
a pointer to allocated data (as a pointer to the first VLA item)
instead of (an unused) integer and to make the caller component
(currently the only one is RNDIS function) responsible for storing
the pointer and freeing the memory when appropriate.

Fixes: 1ae1602d ("configfs: switch ->default groups to a linked list")
Cc: stable@vger.kernel.org
Signed-off-by: default avatarAndrew Gabbasov <andrew_gabbasov@mentor.com>
Signed-off-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
parent aec17e1e
...@@ -1143,11 +1143,12 @@ static struct configfs_attribute *interf_grp_attrs[] = { ...@@ -1143,11 +1143,12 @@ static struct configfs_attribute *interf_grp_attrs[] = {
NULL NULL
}; };
int usb_os_desc_prepare_interf_dir(struct config_group *parent, struct config_group *usb_os_desc_prepare_interf_dir(
int n_interf, struct config_group *parent,
struct usb_os_desc **desc, int n_interf,
char **names, struct usb_os_desc **desc,
struct module *owner) char **names,
struct module *owner)
{ {
struct config_group *os_desc_group; struct config_group *os_desc_group;
struct config_item_type *os_desc_type, *interface_type; struct config_item_type *os_desc_type, *interface_type;
...@@ -1159,7 +1160,7 @@ int usb_os_desc_prepare_interf_dir(struct config_group *parent, ...@@ -1159,7 +1160,7 @@ int usb_os_desc_prepare_interf_dir(struct config_group *parent,
char *vlabuf = kzalloc(vla_group_size(data_chunk), GFP_KERNEL); char *vlabuf = kzalloc(vla_group_size(data_chunk), GFP_KERNEL);
if (!vlabuf) if (!vlabuf)
return -ENOMEM; return ERR_PTR(-ENOMEM);
os_desc_group = vla_ptr(vlabuf, data_chunk, os_desc_group); os_desc_group = vla_ptr(vlabuf, data_chunk, os_desc_group);
os_desc_type = vla_ptr(vlabuf, data_chunk, os_desc_type); os_desc_type = vla_ptr(vlabuf, data_chunk, os_desc_type);
...@@ -1184,7 +1185,7 @@ int usb_os_desc_prepare_interf_dir(struct config_group *parent, ...@@ -1184,7 +1185,7 @@ int usb_os_desc_prepare_interf_dir(struct config_group *parent,
configfs_add_default_group(&d->group, os_desc_group); configfs_add_default_group(&d->group, os_desc_group);
} }
return 0; return os_desc_group;
} }
EXPORT_SYMBOL(usb_os_desc_prepare_interf_dir); EXPORT_SYMBOL(usb_os_desc_prepare_interf_dir);
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
void unregister_gadget_item(struct config_item *item); void unregister_gadget_item(struct config_item *item);
int usb_os_desc_prepare_interf_dir(struct config_group *parent, struct config_group *usb_os_desc_prepare_interf_dir(
int n_interf, struct config_group *parent,
struct usb_os_desc **desc, int n_interf,
char **names, struct usb_os_desc **desc,
struct module *owner); char **names,
struct module *owner);
static inline struct usb_os_desc *to_usb_os_desc(struct config_item *item) static inline struct usb_os_desc *to_usb_os_desc(struct config_item *item)
{ {
......
...@@ -908,6 +908,7 @@ static void rndis_free_inst(struct usb_function_instance *f) ...@@ -908,6 +908,7 @@ static void rndis_free_inst(struct usb_function_instance *f)
free_netdev(opts->net); free_netdev(opts->net);
} }
kfree(opts->rndis_interf_group); /* single VLA chunk */
kfree(opts); kfree(opts);
} }
...@@ -916,6 +917,7 @@ static struct usb_function_instance *rndis_alloc_inst(void) ...@@ -916,6 +917,7 @@ static struct usb_function_instance *rndis_alloc_inst(void)
struct f_rndis_opts *opts; struct f_rndis_opts *opts;
struct usb_os_desc *descs[1]; struct usb_os_desc *descs[1];
char *names[1]; char *names[1];
struct config_group *rndis_interf_group;
opts = kzalloc(sizeof(*opts), GFP_KERNEL); opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts) if (!opts)
...@@ -940,8 +942,14 @@ static struct usb_function_instance *rndis_alloc_inst(void) ...@@ -940,8 +942,14 @@ static struct usb_function_instance *rndis_alloc_inst(void)
names[0] = "rndis"; names[0] = "rndis";
config_group_init_type_name(&opts->func_inst.group, "", config_group_init_type_name(&opts->func_inst.group, "",
&rndis_func_type); &rndis_func_type);
usb_os_desc_prepare_interf_dir(&opts->func_inst.group, 1, descs, rndis_interf_group =
names, THIS_MODULE); usb_os_desc_prepare_interf_dir(&opts->func_inst.group, 1, descs,
names, THIS_MODULE);
if (IS_ERR(rndis_interf_group)) {
rndis_free_inst(&opts->func_inst);
return ERR_CAST(rndis_interf_group);
}
opts->rndis_interf_group = rndis_interf_group;
return &opts->func_inst; return &opts->func_inst;
} }
......
...@@ -26,6 +26,7 @@ struct f_rndis_opts { ...@@ -26,6 +26,7 @@ struct f_rndis_opts {
bool bound; bool bound;
bool borrowed_net; bool borrowed_net;
struct config_group *rndis_interf_group;
struct usb_os_desc rndis_os_desc; struct usb_os_desc rndis_os_desc;
char rndis_ext_compat_id[16]; char rndis_ext_compat_id[16];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment