Skip to contentSkip to conversation

params: fix potential memory leak in add_sysfs_param()

PATCH In review Arjun Sreedharan 4 participants 8 messages Aug 20, 2014 – Aug 21, 2014

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 params: fix potential memory leak in add_sysfs_param()

Do not leak memory when attrs is non NULL and
krealloc() fails. Without temporary variable,
reference to it is lost.

---
 kernel/params.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)
kernel/params.c +7 -9
index 34f5270..b69d683 100644
@@ -594,7 +594,7 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 				     const char *name)
 {
 	struct module_param_attrs *new;
-	struct attribute **attrs;
+	struct attribute **attrs, **new_attrs;
 	int err, num;
 
 	/* We don't bother calling this with invisible parameters. */
@@ -613,15 +613,12 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 		       sizeof(*mk->mp) + sizeof(mk->mp->attrs[0]) * (num+1),
 		       GFP_KERNEL);
 	if (!new) {
-		kfree(attrs);
 		err = -ENOMEM;
 		goto fail;
 	}
-	/* Despite looking like the typical realloc() bug, this is safe.
-	 * We *want* the old 'attrs' to be freed either way, and we'll store
-	 * the new one in the success case. */
-	attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
-	if (!attrs) {
+
+	new_attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
+	if (!new_attrs) {
 		err = -ENOMEM;
 		goto fail_free_new;
 	}
@@ -629,9 +626,9 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 	/* Sysfs wants everything zeroed. */
 	memset(new, 0, sizeof(*new));
 	memset(&new->attrs[num], 0, sizeof(new->attrs[num]));
-	memset(&attrs[num], 0, sizeof(attrs[num]));
+	memset(&new_attrs[num], 0, sizeof(new_attrs[num]));
 	new->grp.name = "parameters";
-	new->grp.attrs = attrs;
+	new->grp.attrs = new_attrs;
 
 	/* Tack new one on the end. */
 	sysfs_attr_init(&new->attrs[num].mattr.attr);
@@ -653,6 +650,7 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 fail_free_new:
 	kfree(new);
 fail:
+	kfree(attrs);
 	mk->mp = NULL;
 	return err;
 }
Signature · 1 line
1.8.1.msysgit.1
#1 lore raw
Quoted 5 lines · Arjun Sreedharan

Do not leak memory when attrs is non NULL and
krealloc() fails. Without temporary variable,
reference to it is lost.

...

kernel/params.c static __modinit int add_sysfs_param(struct module_kobject *mk, · line 621

Quoted 9 lines
kernel/params.c +3 -5
 	}
-	/* Despite looking like the typical realloc() bug, this is safe.
-	 * We *want* the old 'attrs' to be freed either way, and we'll store
-	 * the new one in the success case. */
-	attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
-	if (!attrs) {
+
+	new_attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
+	if (!new_attrs) {

I think that comment you deleted is pretty clear. Is it wrong?

Cheers,
Rusty.

#2 lore raw
Quoted 19 lines · Rusty Russell
Quoted 5 lines · Arjun Sreedharan

Do not leak memory when attrs is non NULL and
krealloc() fails. Without temporary variable,
reference to it is lost.

...

kernel/params.c static __modinit int add_sysfs_param(struct module_kobject *mk, · line 621

Quoted 9 lines
      }
kernel/params.c +3 -5
-     /* Despite looking like the typical realloc() bug, this is safe.
-      * We *want* the old 'attrs' to be freed either way, and we'll store
-      * the new one in the success case. */
-     attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
-     if (!attrs) {
+
+     new_attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
+     if (!new_attrs) {
Quoted 4 lines

I think that comment you deleted is pretty clear. Is it wrong?

Cheers,
Rusty.

I believe it's wrong. I do not understand how this is safe from memory leak. If krealloc() fails, there is nothing in place to free memory held by @attrs

#3 lore raw
Quoted 20 lines · Arjun Sreedharan
Quoted 19 lines · Rusty Russell
Quoted 5 lines · Arjun Sreedharan

Do not leak memory when attrs is non NULL and
krealloc() fails. Without temporary variable,
reference to it is lost.

...

Quoted 9 lines
      }
kernel/params.c +3 -5
-     /* Despite looking like the typical realloc() bug, this is safe.
-      * We *want* the old 'attrs' to be freed either way, and we'll store
-      * the new one in the success case. */
-     attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
-     if (!attrs) {
+
+     new_attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
+     if (!new_attrs) {
Quoted 7 lines
Quoted 4 lines

I think that comment you deleted is pretty clear. Is it wrong?

Cheers,
Rusty.

I believe it's wrong. I do not understand how this is safe from memory leak. If krealloc() fails, there is nothing in place to free memory held by @attrs

Above this:

	if (!mk->mp) {
		num = 0;
		attrs = NULL;
	} else {
		num = mk->mp->num;
		attrs = mk->mp->grp.attrs;
	}

So, attrs is just a temporary: either NULL (doesn't need freeing), or is the old mk->mp->grp.attrs ptr.

But David was the one who placed this comment; I'll let him figure out whether it's bogus or not :)

Cheers,
Rusty.

#4 lore raw
Quoted 10 lines · Rusty Russell

Above this:

        if (!mk->mp) {
                num = 0;
                attrs = NULL;
        } else {
                num = mk->mp->num;
                attrs = mk->mp->grp.attrs;
        }
Quoted 2 lines

So, attrs is just a temporary: either NULL (doesn't need freeing), or is the old mk->mp->grp.attrs ptr.

Except that in the failure case we *free* the old mk->mp and never free mk->mp->grp.attrs so it *is* indeed lost.

A simpler version of Arjun's patch might look like this:

kernel/params.c +4 -2
index 34f5270..f9459bc 100644
@@ -613,7 +613,6 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 		       sizeof(*mk->mp) + sizeof(mk->mp->attrs[0]) * (num+1),
 		       GFP_KERNEL);
 	if (!new) {
-		kfree(attrs);
 		err = -ENOMEM;
 		goto fail;
 	}
@@ -653,7 +652,10 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 fail_free_new:
 	kfree(new);
 fail:
-	mk->mp = NULL;
+	if (mk->mp) {
+		kfree(mk->mp->grp.attrs);
+		mk->mp = NULL;
+	}
 	return err;
 }
 

But as I suggested in my previous response, a *better* fix might look like this:

kernel/params.c +11 -16
index 34f5270..cdab9d4 100644
@@ -595,7 +595,7 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 {
 	struct module_param_attrs *new;
 	struct attribute **attrs;
-	int err, num;
+	int num;
 
 	/* We don't bother calling this with invisible parameters. */
 	BUG_ON(!kp->perm);
@@ -612,18 +612,19 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 	new = krealloc(mk->mp,
 		       sizeof(*mk->mp) + sizeof(mk->mp->attrs[0]) * (num+1),
 		       GFP_KERNEL);
-	if (!new) {
-		kfree(attrs);
-		err = -ENOMEM;
-		goto fail;
-	}
+	if (!new)
+		return -ENOMEM;
+
 	/* Despite looking like the typical realloc() bug, this is safe.
-	 * We *want* the old 'attrs' to be freed either way, and we'll store
-	 * the new one in the success case. */
+	 * In the failure case, the old 'attrs' is still in new->grp.attrs
+	 * and will live on there. */
 	attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
 	if (!attrs) {
-		err = -ENOMEM;
-		goto fail_free_new;
+		/* This is in a larger kmalloc allocation than before but
+		 * otherwise entirely unchanged. We've failed to add the
+		 * new param but the existing ones are still there. */
+		mk->mp = new;
+		return -ENOMEM;
 	}
 
 	/* Sysfs wants everything zeroed. */
@@ -649,12 +650,6 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 
 	mk->mp = new;
 	return 0;
-
-fail_free_new:
-	kfree(new);
-fail:
-	mk->mp = NULL;
-	return err;
 }
 
 #ifdef CONFIG_MODULES
Signature · 2 lines
David Woodhouse                            Open Source Technology Centre
[email protected]                              Intel Corporation
#5 lore raw
Quoted 19 lines · David

Except that in the failure case we *free* the old mk->mp and never free mk->mp->grp.attrs so it *is* indeed lost.

A simpler version of Arjun's patch might look like this:

kernel/params.c +0 -1
index 34f5270..f9459bc 100644
@@ -613,7 +613,6 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
                       sizeof(*mk->mp) + sizeof(mk->mp->attrs[0]) * (num+1),
                       GFP_KERNEL);
        if (!new) {
-               kfree(attrs);
                err = -ENOMEM;
                goto fail;
        }

kernel/params.c static __modinit int add_sysfs_param(struct module_kobject *mk, · line 660

Quoted 10 lines
kernel/params.c +4 -1
@@ -653,7 +652,10 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 fail_free_new:
        kfree(new);
 fail:
-       mk->mp = NULL;
+       if (mk->mp) {
+               kfree(mk->mp->grp.attrs);
+               mk->mp = NULL;
+       }
        return err;
 }

Of course this and Arjun's original *both* fail to fix the classic bug in the 'new = krealloc(mk->mp…)' bit, failing to free the old mk->mp if the realloc fails. I think my alternative fix that didn't kill the old params is probably the better way forward.

Signature · 1 line
dwmw2
#6 lore raw
In reply to Woodhouse, David
Quoted 94 lines · David
Quoted 10 lines · Rusty Russell

Above this:

        if (!mk->mp) {
                num = 0;
                attrs = NULL;
        } else {
                num = mk->mp->num;
                attrs = mk->mp->grp.attrs;
        }
Quoted 2 lines

So, attrs is just a temporary: either NULL (doesn't need freeing), or is the old mk->mp->grp.attrs ptr.

Except that in the failure case we *free* the old mk->mp and never free mk->mp->grp.attrs so it *is* indeed lost.

A simpler version of Arjun's patch might look like this:

kernel/params.c +4 -2
index 34f5270..f9459bc 100644
@@ -613,7 +613,6 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
                       sizeof(*mk->mp) + sizeof(mk->mp->attrs[0]) * (num+1),
                       GFP_KERNEL);
        if (!new) {
-               kfree(attrs);
                err = -ENOMEM;
                goto fail;
        }
@@ -653,7 +652,10 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 fail_free_new:
        kfree(new);
 fail:
-       mk->mp = NULL;
+       if (mk->mp) {
+               kfree(mk->mp->grp.attrs);
+               mk->mp = NULL;
+       }
        return err;
 }
 

But as I suggested in my previous response, a *better* fix might look like this:

kernel/params.c +11 -10
index 34f5270..cdab9d4 100644
@@ -595,7 +595,7 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 {
        struct module_param_attrs *new;
        struct attribute **attrs;
-       int err, num;
+       int num;
 
        /* We don't bother calling this with invisible parameters. */
        BUG_ON(!kp->perm);
@@ -612,18 +612,19 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
        new = krealloc(mk->mp,
                       sizeof(*mk->mp) + sizeof(mk->mp->attrs[0]) * (num+1),
                       GFP_KERNEL);
-       if (!new) {
-               kfree(attrs);
-               err = -ENOMEM;
-               goto fail;
-       }
+       if (!new)
+               return -ENOMEM;
+
        /* Despite looking like the typical realloc() bug, this is safe.
-        * We *want* the old 'attrs' to be freed either way, and we'll store
-        * the new one in the success case. */
+        * In the failure case, the old 'attrs' is still in new->grp.attrs
+        * and will live on there. */
        attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
        if (!attrs) {
-               err = -ENOMEM;
-               goto fail_free_new;
+               /* This is in a larger kmalloc allocation than before but
+                * otherwise entirely unchanged. We've failed to add the
+                * new param but the existing ones are still there. */
+               mk->mp = new;
+               return -ENOMEM;
        }
 
        /* Sysfs wants everything zeroed. */
@@ -649,12 +650,6 @@ static __modinit int add_sysfs_param(struct module_kobject *mk,
 
        mk->mp = new;

kernel/params.c static __modinit int add_sysfs_param(struct module_kobject *mk, · line 655

Quoted 10 lines
kernel/params.c +0 -6
        return 0;
-
-fail_free_new:
-       kfree(new);
-fail:
-       mk->mp = NULL;
-       return err;
 }
 
 #ifdef CONFIG_MODULES

Ok, what's the harm in releasing {new,mk->mp}->grp.attrs and new/mk->mp on realloc failure ?
@David, @Rusty Am i right thinking they are not used after the function returns error ?

See module_param_sysfs_setup() : when the function in question returns error, free_module_param_attrs() is *not* called to release memory.

For cleaner code, we do *not* release memory in the said function as David suggests and then call
free_module_param_attrs() on returning err. What say?

Arjun

Quoted 4 lines

--
David Woodhouse Open Source Technology Centre
[email protected] Intel Corporation

#7 lore raw
In reply to Rusty Russell
Quoted 9 lines · Rusty Russell
Quoted 5 lines · Arjun Sreedharan

Do not leak memory when attrs is non NULL and
krealloc() fails. Without temporary variable,
reference to it is lost.

...

Quoted 11 lines

kernel/params.c static __modinit int add_sysfs_param(struct module_kobject *mk, · line 621

Quoted 9 lines
kernel/params.c +3 -5
 	}
-	/* Despite looking like the typical realloc() bug, this is safe.
-	 * We *want* the old 'attrs' to be freed either way, and we'll store
-	 * the new one in the success case. */
-	attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
-	if (!attrs) {
+
+	new_attrs = krealloc(attrs, sizeof(new->grp.attrs[0])*(num+2), GFP_KERNEL);
+	if (!new_attrs) {

I think that comment you deleted is pretty clear. Is it wrong?

Yes, I think it's wrong.

I think I was under the impression that we shouldn't be freeing the old 'attrs' pointer on failure because it would live on in {new,mk->mp}->grp.attrs and still be used. (Which I concede isn't actually what the comment says, making it doubly wrong.)

I may have failed to notice that when this krealloc() fails, we actually *free* the 'new' pointer and set mk->mp to NULL. Without freeing the new->grp.attrs pointer. So it is indeed leaked. We *don't* need a temporary variable for it though; we can find it.

In fact, I wonder if it's better to change the 'goto fail_free_new' in the failure path here to instead do something like 'mk->mp = new; return -ENOMEM;'. That way, the existing params would still be present and we'd just have failed to add the *new* one. (And the leak wouldn't exist because the 'attrs' pointer would live on as I originally thought.)

Signature · 2 lines
David Woodhouse                            Open Source Technology Centre
[email protected]                              Intel Corporation