diff mbox series

[v2,1/3] efi_loader: avoid pointer access after calling efi_delete_handle

Message ID 20240117094432.1049168-2-masahisa.kojima@linaro.org
State Superseded
Headers show
Series fix and refactoring of efi_disk.c | expand

Commit Message

Masahisa Kojima Jan. 17, 2024, 9:44 a.m. UTC
efi_delete_handle() calls efi_purge_handle(), then it finally
frees the EFI handle.
Both diskobj and handle variables in efi_disk_remove() have
the same pointer, we can not access diskobj->dp after calling
efi_delete_handle().

This commit saves the struct efi_device_path pointer before
calling efi_delete_handle(). This commit also fixes the
missing free for volume member in struct efi_disk_obj.

This commit also removes the container_of() calls, and
adds the TODO comment of missing efi_close_protocol() call
for the parent EFI_BLOCK_IO_PROTOCOL.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
 lib/efi_loader/efi_disk.c | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

Comments

Ilias Apalodimas Jan. 17, 2024, 11:51 a.m. UTC | #1
Hi Kojima-san

On Wed, 17 Jan 2024 at 11:46, Masahisa Kojima
<masahisa.kojima@linaro.org> wrote:
>
> efi_delete_handle() calls efi_purge_handle(), then it finally
> frees the EFI handle.
> Both diskobj and handle variables in efi_disk_remove() have
> the same pointer, we can not access diskobj->dp after calling
> efi_delete_handle().
>
> This commit saves the struct efi_device_path pointer before
> calling efi_delete_handle(). This commit also fixes the
> missing free for volume member in struct efi_disk_obj.
>
> This commit also removes the container_of() calls, and
> adds the TODO comment of missing efi_close_protocol() call
> for the parent EFI_BLOCK_IO_PROTOCOL.
>
> Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> ---
>  lib/efi_loader/efi_disk.c | 23 +++++++++++++++++------
>  1 file changed, 17 insertions(+), 6 deletions(-)
>
> diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
> index 013842f077..ce46c1092a 100644
> --- a/lib/efi_loader/efi_disk.c
> +++ b/lib/efi_loader/efi_disk.c
> @@ -707,35 +707,46 @@ int efi_disk_remove(void *ctx, struct event *event)
>         struct udevice *dev = event->data.dm.dev;
>         efi_handle_t handle;
>         struct blk_desc *desc;
> +       struct efi_device_path *dp = NULL;
>         struct efi_disk_obj *diskobj = NULL;
> +       struct efi_simple_file_system_protocol *volume = NULL;
>         efi_status_t ret;
>
>         if (dev_tag_get_ptr(dev, DM_TAG_EFI, (void **)&handle))
>                 return 0;
>
> +       if (!handle)
> +               return 0;
> +

Is there a chance dev_tag_get_ptr() will return a NULL handle?

>         id = device_get_uclass_id(dev);
>         switch (id) {
>         case UCLASS_BLK:
>                 desc = dev_get_uclass_plat(dev);
>                 if (desc && desc->uclass_id != UCLASS_EFI_LOADER)
> -                       diskobj = container_of(handle, struct efi_disk_obj,
> -                                              header);
> +                       diskobj = (struct efi_disk_obj *)handle;
>                 break;
>         case UCLASS_PARTITION:
> -               diskobj = container_of(handle, struct efi_disk_obj, header);
> +               diskobj = (struct efi_disk_obj *)handle;
> +
> +               /* TODO: closing the parent EFI_BLOCK_IO_PROTOCOL is missing. */
> +
>                 break;
>         default:
>                 return 0;
>         }
>
> +       if (diskobj) {
> +               dp = diskobj->dp;
> +               volume = diskobj->volume;
> +       }
> +
>         ret = efi_delete_handle(handle);

Hmm, in which context are the dp and volume pointers allocated? The
handle is now freed, can we reliably access dp and volume ptrs?

>         /* Do not delete DM device if there are still EFI drivers attached. */
>         if (ret != EFI_SUCCESS)
>                 return -1;
>
> -       if (diskobj)
> -               efi_free_pool(diskobj->dp);
> -
> +       efi_free_pool(dp);
> +       free(volume);
>         dev_tag_del(dev, DM_TAG_EFI);
>
>         return 0;
> --
> 2.34.1
>

Thanks
/Ilias
Masahisa Kojima Jan. 18, 2024, 12:16 a.m. UTC | #2
On Wed, 17 Jan 2024 at 20:52, Ilias Apalodimas
<ilias.apalodimas@linaro.org> wrote:
>
> Hi Kojima-san
>
> On Wed, 17 Jan 2024 at 11:46, Masahisa Kojima
> <masahisa.kojima@linaro.org> wrote:
> >
> > efi_delete_handle() calls efi_purge_handle(), then it finally
> > frees the EFI handle.
> > Both diskobj and handle variables in efi_disk_remove() have
> > the same pointer, we can not access diskobj->dp after calling
> > efi_delete_handle().
> >
> > This commit saves the struct efi_device_path pointer before
> > calling efi_delete_handle(). This commit also fixes the
> > missing free for volume member in struct efi_disk_obj.
> >
> > This commit also removes the container_of() calls, and
> > adds the TODO comment of missing efi_close_protocol() call
> > for the parent EFI_BLOCK_IO_PROTOCOL.
> >
> > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > ---
> >  lib/efi_loader/efi_disk.c | 23 +++++++++++++++++------
> >  1 file changed, 17 insertions(+), 6 deletions(-)
> >
> > diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
> > index 013842f077..ce46c1092a 100644
> > --- a/lib/efi_loader/efi_disk.c
> > +++ b/lib/efi_loader/efi_disk.c
> > @@ -707,35 +707,46 @@ int efi_disk_remove(void *ctx, struct event *event)
> >         struct udevice *dev = event->data.dm.dev;
> >         efi_handle_t handle;
> >         struct blk_desc *desc;
> > +       struct efi_device_path *dp = NULL;
> >         struct efi_disk_obj *diskobj = NULL;
> > +       struct efi_simple_file_system_protocol *volume = NULL;
> >         efi_status_t ret;
> >
> >         if (dev_tag_get_ptr(dev, DM_TAG_EFI, (void **)&handle))
> >                 return 0;
> >
> > +       if (!handle)
> > +               return 0;
> > +
>
> Is there a chance dev_tag_get_ptr() will return a NULL handle?

There is no chance unless we actually set a NULL.
Checking the return value of dev_tag_get_ptr above is enough, I will
remove this.

>
> >         id = device_get_uclass_id(dev);
> >         switch (id) {
> >         case UCLASS_BLK:
> >                 desc = dev_get_uclass_plat(dev);
> >                 if (desc && desc->uclass_id != UCLASS_EFI_LOADER)
> > -                       diskobj = container_of(handle, struct efi_disk_obj,
> > -                                              header);
> > +                       diskobj = (struct efi_disk_obj *)handle;
> >                 break;
> >         case UCLASS_PARTITION:
> > -               diskobj = container_of(handle, struct efi_disk_obj, header);
> > +               diskobj = (struct efi_disk_obj *)handle;
> > +
> > +               /* TODO: closing the parent EFI_BLOCK_IO_PROTOCOL is missing. */
> > +
> >                 break;
> >         default:
> >                 return 0;
> >         }
> >
> > +       if (diskobj) {
> > +               dp = diskobj->dp;
> > +               volume = diskobj->volume;
> > +       }
> > +
> >         ret = efi_delete_handle(handle);
>
> Hmm, in which context are the dp and volume pointers allocated? The
> handle is now freed, can we reliably access dp and volume ptrs?

dp and volume are allocated in efi_disk_add_dev() when the block
device is probed.
efi_delete_handle() will remove all protocols from the handle,
efi_uninstall_protocol()
checks whether protocol_interface is actually installed. So dp and
volume should be
freed after efi_delete_handle().

Thanks,
Masahisa Kojima

>
> >         /* Do not delete DM device if there are still EFI drivers attached. */
> >         if (ret != EFI_SUCCESS)
> >                 return -1;
> >
> > -       if (diskobj)
> > -               efi_free_pool(diskobj->dp);
> > -
> > +       efi_free_pool(dp);
> > +       free(volume);
> >         dev_tag_del(dev, DM_TAG_EFI);
> >
> >         return 0;
> > --
> > 2.34.1
> >
>
> Thanks
> /Ilias
Ilias Apalodimas Jan. 18, 2024, 6:13 a.m. UTC | #3
On Thu, 18 Jan 2024 at 02:16, Masahisa Kojima
<masahisa.kojima@linaro.org> wrote:
>
> On Wed, 17 Jan 2024 at 20:52, Ilias Apalodimas
> <ilias.apalodimas@linaro.org> wrote:
> >
> > Hi Kojima-san
> >
> > On Wed, 17 Jan 2024 at 11:46, Masahisa Kojima
> > <masahisa.kojima@linaro.org> wrote:
> > >
> > > efi_delete_handle() calls efi_purge_handle(), then it finally
> > > frees the EFI handle.
> > > Both diskobj and handle variables in efi_disk_remove() have
> > > the same pointer, we can not access diskobj->dp after calling
> > > efi_delete_handle().
> > >
> > > This commit saves the struct efi_device_path pointer before
> > > calling efi_delete_handle(). This commit also fixes the
> > > missing free for volume member in struct efi_disk_obj.
> > >
> > > This commit also removes the container_of() calls, and
> > > adds the TODO comment of missing efi_close_protocol() call
> > > for the parent EFI_BLOCK_IO_PROTOCOL.
> > >
> > > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > > ---
> > >  lib/efi_loader/efi_disk.c | 23 +++++++++++++++++------
> > >  1 file changed, 17 insertions(+), 6 deletions(-)
> > >
> > > diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
> > > index 013842f077..ce46c1092a 100644
> > > --- a/lib/efi_loader/efi_disk.c
> > > +++ b/lib/efi_loader/efi_disk.c
> > > @@ -707,35 +707,46 @@ int efi_disk_remove(void *ctx, struct event *event)
> > >         struct udevice *dev = event->data.dm.dev;
> > >         efi_handle_t handle;
> > >         struct blk_desc *desc;
> > > +       struct efi_device_path *dp = NULL;
> > >         struct efi_disk_obj *diskobj = NULL;
> > > +       struct efi_simple_file_system_protocol *volume = NULL;
> > >         efi_status_t ret;
> > >
> > >         if (dev_tag_get_ptr(dev, DM_TAG_EFI, (void **)&handle))
> > >                 return 0;
> > >
> > > +       if (!handle)
> > > +               return 0;
> > > +
> >
> > Is there a chance dev_tag_get_ptr() will return a NULL handle?
>
> There is no chance unless we actually set a NULL.
> Checking the return value of dev_tag_get_ptr above is enough, I will
> remove this.
>
> >
> > >         id = device_get_uclass_id(dev);
> > >         switch (id) {
> > >         case UCLASS_BLK:
> > >                 desc = dev_get_uclass_plat(dev);
> > >                 if (desc && desc->uclass_id != UCLASS_EFI_LOADER)
> > > -                       diskobj = container_of(handle, struct efi_disk_obj,
> > > -                                              header);
> > > +                       diskobj = (struct efi_disk_obj *)handle;
> > >                 break;
> > >         case UCLASS_PARTITION:
> > > -               diskobj = container_of(handle, struct efi_disk_obj, header);
> > > +               diskobj = (struct efi_disk_obj *)handle;
> > > +
> > > +               /* TODO: closing the parent EFI_BLOCK_IO_PROTOCOL is missing. */
> > > +
> > >                 break;
> > >         default:
> > >                 return 0;
> > >         }
> > >
> > > +       if (diskobj) {
> > > +               dp = diskobj->dp;
> > > +               volume = diskobj->volume;
> > > +       }
> > > +
> > >         ret = efi_delete_handle(handle);
> >
> > Hmm, in which context are the dp and volume pointers allocated? The
> > handle is now freed, can we reliably access dp and volume ptrs?
>
> dp and volume are allocated in efi_disk_add_dev() when the block
> device is probed.
> efi_delete_handle() will remove all protocols from the handle,
> efi_uninstall_protocol()
> checks whether protocol_interface is actually installed. So dp and
> volume should be
> freed after efi_delete_handle().

Ok that makes sense,

With the check above removed
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>


>
> Thanks,
> Masahisa Kojima
>
> >
> > >         /* Do not delete DM device if there are still EFI drivers attached. */
> > >         if (ret != EFI_SUCCESS)
> > >                 return -1;
> > >
> > > -       if (diskobj)
> > > -               efi_free_pool(diskobj->dp);
> > > -
> > > +       efi_free_pool(dp);
> > > +       free(volume);
> > >         dev_tag_del(dev, DM_TAG_EFI);
> > >
> > >         return 0;
> > > --
> > > 2.34.1
> > >
> >
> > Thanks
> > /Ilias
diff mbox series

Patch

diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
index 013842f077..ce46c1092a 100644
--- a/lib/efi_loader/efi_disk.c
+++ b/lib/efi_loader/efi_disk.c
@@ -707,35 +707,46 @@  int efi_disk_remove(void *ctx, struct event *event)
 	struct udevice *dev = event->data.dm.dev;
 	efi_handle_t handle;
 	struct blk_desc *desc;
+	struct efi_device_path *dp = NULL;
 	struct efi_disk_obj *diskobj = NULL;
+	struct efi_simple_file_system_protocol *volume = NULL;
 	efi_status_t ret;
 
 	if (dev_tag_get_ptr(dev, DM_TAG_EFI, (void **)&handle))
 		return 0;
 
+	if (!handle)
+		return 0;
+
 	id = device_get_uclass_id(dev);
 	switch (id) {
 	case UCLASS_BLK:
 		desc = dev_get_uclass_plat(dev);
 		if (desc && desc->uclass_id != UCLASS_EFI_LOADER)
-			diskobj = container_of(handle, struct efi_disk_obj,
-					       header);
+			diskobj = (struct efi_disk_obj *)handle;
 		break;
 	case UCLASS_PARTITION:
-		diskobj = container_of(handle, struct efi_disk_obj, header);
+		diskobj = (struct efi_disk_obj *)handle;
+
+		/* TODO: closing the parent EFI_BLOCK_IO_PROTOCOL is missing. */
+
 		break;
 	default:
 		return 0;
 	}
 
+	if (diskobj) {
+		dp = diskobj->dp;
+		volume = diskobj->volume;
+	}
+
 	ret = efi_delete_handle(handle);
 	/* Do not delete DM device if there are still EFI drivers attached. */
 	if (ret != EFI_SUCCESS)
 		return -1;
 
-	if (diskobj)
-		efi_free_pool(diskobj->dp);
-
+	efi_free_pool(dp);
+	free(volume);
 	dev_tag_del(dev, DM_TAG_EFI);
 
 	return 0;