diff mbox

drm/exynos: fix memory leak: free EDID block

Message ID 1353403816-8091-1-git-send-email-eich@suse.de (mailing list archive)
State New, archived
Headers show

Commit Message

Egbert Eich Nov. 20, 2012, 9:30 a.m. UTC
drm_get_edid() returns a pointer to an EDID block. The caller
is responsible to free this pointer itself.
Here the pointer gets assigned to the local variable raw_edid.
Therefore it should be freed before the variable goes out of
scope.

Signed-off-by: Egbert Eich <eich@suse.de>
---
 drivers/gpu/drm/exynos/exynos_hdmi.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Comments

Sean Paul Nov. 20, 2012, 7:58 p.m. UTC | #1
On Tue, Nov 20, 2012 at 4:30 AM, Egbert Eich <eich@suse.de> wrote:
> drm_get_edid() returns a pointer to an EDID block. The caller
> is responsible to free this pointer itself.
> Here the pointer gets assigned to the local variable raw_edid.
> Therefore it should be freed before the variable goes out of
> scope.
>
> Signed-off-by: Egbert Eich <eich@suse.de>
> ---
>  drivers/gpu/drm/exynos/exynos_hdmi.c |    1 +
>  1 files changed, 1 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
> index 2c115f8..bc87bca 100644
> --- a/drivers/gpu/drm/exynos/exynos_hdmi.c
> +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
> @@ -1293,6 +1293,7 @@ static int hdmi_get_edid(void *ctx, struct drm_connector *connector,
>                 DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n",
>                         (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
>                         raw_edid->width_cm, raw_edid->height_cm);
> +               kfree(raw_edid);

This will actually cause the memory to be freed twice.

The reason this happens is drm_get_edid attaches this to
connector->display_info.raw_edid, which is then freed in the
exynos_drm_connector function that gets the edid.

The whole thing is ugly, and needs to be revised. I've uploaded a
patch to refactor this against the chromium tree, but haven't yet
rebased against upstream. See
https://gerrit.chromium.org/gerrit/#/c/38406/

For now, please drop this patch.

Sean

>         } else {
>                 return -ENODEV;
>         }
> --
> 1.7.7
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
Egbert Eich Nov. 20, 2012, 8:29 p.m. UTC | #2
Sean Paul writes:
 > On Tue, Nov 20, 2012 at 4:30 AM, Egbert Eich <eich@suse.de> wrote:
 > > drm_get_edid() returns a pointer to an EDID block. The caller
 > > is responsible to free this pointer itself.
 > > Here the pointer gets assigned to the local variable raw_edid.
 > > Therefore it should be freed before the variable goes out of
 > > scope.
 > >
 > > Signed-off-by: Egbert Eich <eich@suse.de>
 > > ---
 > >  drivers/gpu/drm/exynos/exynos_hdmi.c |    1 +
 > >  1 files changed, 1 insertions(+), 0 deletions(-)
 > >
 > > diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
 > > index 2c115f8..bc87bca 100644
 > > --- a/drivers/gpu/drm/exynos/exynos_hdmi.c
 > > +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
 > > @@ -1293,6 +1293,7 @@ static int hdmi_get_edid(void *ctx, struct drm_connector *connector,
 > >                 DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n",
 > >                         (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
 > >                         raw_edid->width_cm, raw_edid->height_cm);
 > > +               kfree(raw_edid);
 > 
 > This will actually cause the memory to be freed twice.
 > 
 > The reason this happens is drm_get_edid attaches this to
 > connector->display_info.raw_edid, which is then freed in the
 > exynos_drm_connector function that gets the edid.

No, the raw_edid member is gone from struct drm_display_info.
All other drivers free the edid data returned by drm_get_edid() 
themselves.
I would actually prefer if the edid data would be freed by
drm_connector_destroy rather than by the drivers as this would
allow us to cache an edid without creating copies to pass to
the drivers. However some drivers store the pointer to returned 
edid block somewhere in their own data structures, so if we
do this it can easily get out of sync when we reread the
edid and it has changed for whatever reason in which case
the driver will have a stale and invalid pointer.

 > 
 > The whole thing is ugly, and needs to be revised. I've uploaded a
 > patch to refactor this against the chromium tree, but haven't yet
 > rebased against upstream. See
 > https://gerrit.chromium.org/gerrit/#/c/38406/
 > 
 > For now, please drop this patch.
 > 

I've looked at the code in the exynos driver in the drm_next
branch - there's nothing that destroys this edid data any more.

Cheers,
	Egbert.
Sean Paul Nov. 20, 2012, 9:18 p.m. UTC | #3
On Tue, Nov 20, 2012 at 3:29 PM, Egbert Eich <eich@suse.com> wrote:
> Sean Paul writes:
>  > On Tue, Nov 20, 2012 at 4:30 AM, Egbert Eich <eich@suse.de> wrote:
>  > > drm_get_edid() returns a pointer to an EDID block. The caller
>  > > is responsible to free this pointer itself.
>  > > Here the pointer gets assigned to the local variable raw_edid.
>  > > Therefore it should be freed before the variable goes out of
>  > > scope.
>  > >
>  > > Signed-off-by: Egbert Eich <eich@suse.de>

Reviewed-by: Sean Paul <seanpaul@chromium.org>

>  > > ---
>  > >  drivers/gpu/drm/exynos/exynos_hdmi.c |    1 +
>  > >  1 files changed, 1 insertions(+), 0 deletions(-)
>  > >
>  > > diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
>  > > index 2c115f8..bc87bca 100644
>  > > --- a/drivers/gpu/drm/exynos/exynos_hdmi.c
>  > > +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
>  > > @@ -1293,6 +1293,7 @@ static int hdmi_get_edid(void *ctx, struct drm_connector *connector,
>  > >                 DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n",
>  > >                         (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
>  > >                         raw_edid->width_cm, raw_edid->height_cm);
>  > > +               kfree(raw_edid);
>  >
>  > This will actually cause the memory to be freed twice.
>  >
>  > The reason this happens is drm_get_edid attaches this to
>  > connector->display_info.raw_edid, which is then freed in the
>  > exynos_drm_connector function that gets the edid.
>
> No, the raw_edid member is gone from struct drm_display_info.
> All other drivers free the edid data returned by drm_get_edid()
> themselves.

A ha! I should have checked first, my apologies.

> I would actually prefer if the edid data would be freed by
> drm_connector_destroy rather than by the drivers as this would
> allow us to cache an edid without creating copies to pass to
> the drivers. However some drivers store the pointer to returned
> edid block somewhere in their own data structures, so if we
> do this it can easily get out of sync when we reread the
> edid and it has changed for whatever reason in which case
> the driver will have a stale and invalid pointer.
>

Agreed, that would be nice.

>  >
>  > The whole thing is ugly, and needs to be revised. I've uploaded a
>  > patch to refactor this against the chromium tree, but haven't yet
>  > rebased against upstream. See
>  > https://gerrit.chromium.org/gerrit/#/c/38406/
>  >
>  > For now, please drop this patch.
>  >
>
> I've looked at the code in the exynos driver in the drm_next
> branch - there's nothing that destroys this edid data any more.
>

Yep, right you are. Your patch does the right thing, thanks for
setting me straight :)

I hope we won't need to live with this double-allocation + copy for too long.

Sean



> Cheers,
>         Egbert.
Inki Dae Nov. 21, 2012, 3:50 a.m. UTC | #4
Ok, I modifed the subject of your patch like below and applied.

drm/exynos: fix memory lean to EDID block


Thanks,
Inki Dae

2012/11/20 Egbert Eich <eich@suse.de>

> drm_get_edid() returns a pointer to an EDID block. The caller
> is responsible to free this pointer itself.
> Here the pointer gets assigned to the local variable raw_edid.
> Therefore it should be freed before the variable goes out of
> scope.
>
> Signed-off-by: Egbert Eich <eich@suse.de>
> ---
>  drivers/gpu/drm/exynos/exynos_hdmi.c |    1 +
>  1 files changed, 1 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c
> b/drivers/gpu/drm/exynos/exynos_hdmi.c
> index 2c115f8..bc87bca 100644
> --- a/drivers/gpu/drm/exynos/exynos_hdmi.c
> +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
> @@ -1293,6 +1293,7 @@ static int hdmi_get_edid(void *ctx, struct
> drm_connector *connector,
>                 DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n",
>                         (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
>                         raw_edid->width_cm, raw_edid->height_cm);
> +               kfree(raw_edid);
>         } else {
>                 return -ENODEV;
>         }
> --
> 1.7.7
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
>
Jani Nikula Dec. 14, 2012, 10:24 a.m. UTC | #5
On Tue, 20 Nov 2012, Sean Paul <seanpaul@chromium.org> wrote:
> On Tue, Nov 20, 2012 at 4:30 AM, Egbert Eich <eich@suse.de> wrote:
>> drm_get_edid() returns a pointer to an EDID block. The caller
>> is responsible to free this pointer itself.
>> Here the pointer gets assigned to the local variable raw_edid.
>> Therefore it should be freed before the variable goes out of
>> scope.
>>
>> Signed-off-by: Egbert Eich <eich@suse.de>
>> ---
>>  drivers/gpu/drm/exynos/exynos_hdmi.c |    1 +
>>  1 files changed, 1 insertions(+), 0 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
>> index 2c115f8..bc87bca 100644
>> --- a/drivers/gpu/drm/exynos/exynos_hdmi.c
>> +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
>> @@ -1293,6 +1293,7 @@ static int hdmi_get_edid(void *ctx, struct drm_connector *connector,
>>                 DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n",
>>                         (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
>>                         raw_edid->width_cm, raw_edid->height_cm);
>> +               kfree(raw_edid);
>
> This will actually cause the memory to be freed twice.
>
> The reason this happens is drm_get_edid attaches this to
> connector->display_info.raw_edid, which is then freed in the
> exynos_drm_connector function that gets the edid.
>
> The whole thing is ugly, and needs to be revised. I've uploaded a
> patch to refactor this against the chromium tree, but haven't yet
> rebased against upstream. See
> https://gerrit.chromium.org/gerrit/#/c/38406/

The patch is good. connector->display_info.raw_edid is gone since

commit 451023dc32d4542c21b52ad1692e6e01cb75b099
Author: Jani Nikula <jani.nikula@intel.com>
Date:   Wed Aug 15 09:32:39 2012 +0000

    drm: remove the raw_edid field from struct drm_display_info

BR,
Jani.


>
> For now, please drop this patch.
>
> Sean
>
>>         } else {
>>                 return -ENODEV;
>>         }
>> --
>> 1.7.7
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> http://lists.freedesktop.org/mailman/listinfo/dri-devel
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
diff mbox

Patch

diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
index 2c115f8..bc87bca 100644
--- a/drivers/gpu/drm/exynos/exynos_hdmi.c
+++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
@@ -1293,6 +1293,7 @@  static int hdmi_get_edid(void *ctx, struct drm_connector *connector,
 		DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n",
 			(hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
 			raw_edid->width_cm, raw_edid->height_cm);
+		kfree(raw_edid);
 	} else {
 		return -ENODEV;
 	}