2022-03-27 18:09:50

by Xiaomeng Tong

[permalink] [raw]
Subject: [PATCH] opp: fix a missing check on list iterator

The bug is here:
dev = new_dev->dev;

The list iterator 'new_dev' will point to a bogus position containing
HEAD if the list is empty or no element is found. This case must
be checked before any use of the iterator, otherwise it will lead
to a invalid memory access.

To fix this bug, add an check. Use a new variable 'iter' as the
list iterator, while use the old variable 'new_dev' as a dedicated
pointer to point to the found element.

Cc: [email protected]
Fixes: deaa51465105a ("PM / OPP: Add debugfs support")
Signed-off-by: Xiaomeng Tong <[email protected]>
---
drivers/opp/debugfs.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/opp/debugfs.c b/drivers/opp/debugfs.c
index 596c185b5dda..a4476985e4ce 100644
--- a/drivers/opp/debugfs.c
+++ b/drivers/opp/debugfs.c
@@ -187,14 +187,19 @@ void opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table)
static void opp_migrate_dentry(struct opp_device *opp_dev,
struct opp_table *opp_table)
{
- struct opp_device *new_dev;
+ struct opp_device *new_dev = NULL, *iter;
const struct device *dev;
struct dentry *dentry;

/* Look for next opp-dev */
- list_for_each_entry(new_dev, &opp_table->dev_list, node)
- if (new_dev != opp_dev)
+ list_for_each_entry(iter, &opp_table->dev_list, node)
+ if (iter != opp_dev) {
+ new_dev = iter;
break;
+ }
+
+ if (!new_dev)
+ return;

/* new_dev is guaranteed to be valid here */
dev = new_dev->dev;
--
2.17.1


2022-03-28 09:58:04

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH] opp: fix a missing check on list iterator

On 27-03-22, 13:39, Xiaomeng Tong wrote:
> The bug is here:
> dev = new_dev->dev;
>
> The list iterator 'new_dev' will point to a bogus position containing
> HEAD if the list is empty or no element is found. This case must
> be checked before any use of the iterator, otherwise it will lead
> to a invalid memory access.
>
> To fix this bug, add an check. Use a new variable 'iter' as the
> list iterator, while use the old variable 'new_dev' as a dedicated
> pointer to point to the found element.
>
> Cc: [email protected]
> Fixes: deaa51465105a ("PM / OPP: Add debugfs support")
> Signed-off-by: Xiaomeng Tong <[email protected]>
> ---
> drivers/opp/debugfs.c | 11 ++++++++---
> 1 file changed, 8 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/opp/debugfs.c b/drivers/opp/debugfs.c
> index 596c185b5dda..a4476985e4ce 100644
> --- a/drivers/opp/debugfs.c
> +++ b/drivers/opp/debugfs.c
> @@ -187,14 +187,19 @@ void opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table)
> static void opp_migrate_dentry(struct opp_device *opp_dev,
> struct opp_table *opp_table)
> {
> - struct opp_device *new_dev;
> + struct opp_device *new_dev = NULL, *iter;
> const struct device *dev;
> struct dentry *dentry;
>
> /* Look for next opp-dev */
> - list_for_each_entry(new_dev, &opp_table->dev_list, node)
> - if (new_dev != opp_dev)
> + list_for_each_entry(iter, &opp_table->dev_list, node)
> + if (iter != opp_dev) {
> + new_dev = iter;
> break;
> + }
> +
> + if (!new_dev)
> + return;

I think you missed this check in the parent function ?

if (!list_is_singular(&opp_table->dev_list)) {


i.e. this bug can never happen.

--
viresh

2022-03-28 12:10:27

by Xiaomeng Tong

[permalink] [raw]
Subject: Re: [PATCH] opp: fix a missing check on list iterator

On Mon, 28 Mar 2022 14:20:57 +0530, Viresh Kumar wrote:
> On 28-03-22, 15:43, Xiaomeng Tong wrote:
> > No. the conditon to call opp_migrate_dentry(opp_dev, opp_table); is:
> > if (!list_is_singular(&opp_table->dev_list)),
> >
> > while list_is_singlular is: !list_empty(head) && (head->next == head->prev);
> >
> > so the condition is: list_empty(head) || (head->next != head->prev)
> >
> > if the list is empty, the bug can be triggered.
>
> List can't be empty here by design. It will be a huge bug in that
> case, which should lead to crash somewhere.
>

There is anther condition to trigger this bug: the list is not empty and
no element found (if (iter != opp_dev)).

--
Xiaomeng Tong

2022-03-28 19:50:40

by Xiaomeng Tong

[permalink] [raw]
Subject: Re: [PATCH] opp: fix a missing check on list iterator

On Mon, 28 Mar 2022 08:47:39 +0530, Viresh Kumar wrote:
> > diff --git a/drivers/opp/debugfs.c b/drivers/opp/debugfs.c
> > index 596c185b5dda..a4476985e4ce 100644
> > --- a/drivers/opp/debugfs.c
> > +++ b/drivers/opp/debugfs.c
> > @@ -187,14 +187,19 @@ void opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table)
> > static void opp_migrate_dentry(struct opp_device *opp_dev,
> > struct opp_table *opp_table)
> > {
> > - struct opp_device *new_dev;
> > + struct opp_device *new_dev = NULL, *iter;
> > const struct device *dev;
> > struct dentry *dentry;
> >
> > /* Look for next opp-dev */
> > - list_for_each_entry(new_dev, &opp_table->dev_list, node)
> > - if (new_dev != opp_dev)
> > + list_for_each_entry(iter, &opp_table->dev_list, node)
> > + if (iter != opp_dev) {
> > + new_dev = iter;
> > break;
> > + }
> > +
> > + if (!new_dev)
> > + return;
>
> I think you missed this check in the parent function ?
>
> if (!list_is_singular(&opp_table->dev_list)) {
>
>
> i.e. this bug can never happen.
>

No. the conditon to call opp_migrate_dentry(opp_dev, opp_table); is:
if (!list_is_singular(&opp_table->dev_list)),

while list_is_singlular is: !list_empty(head) && (head->next == head->prev);

so the condition is: list_empty(head) || (head->next != head->prev)

if the list is empty, the bug can be triggered.

--
Xiaomeng Tong

2022-03-28 20:57:34

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH] opp: fix a missing check on list iterator

On 28-03-22, 17:13, Xiaomeng Tong wrote:
> On Mon, 28 Mar 2022 14:20:57 +0530, Viresh Kumar wrote:
> > On 28-03-22, 15:43, Xiaomeng Tong wrote:
> > > No. the conditon to call opp_migrate_dentry(opp_dev, opp_table); is:
> > > if (!list_is_singular(&opp_table->dev_list)),
> > >
> > > while list_is_singlular is: !list_empty(head) && (head->next == head->prev);
> > >
> > > so the condition is: list_empty(head) || (head->next != head->prev)
> > >
> > > if the list is empty, the bug can be triggered.
> >
> > List can't be empty here by design. It will be a huge bug in that
> > case, which should lead to crash somewhere.
> >
>
> There is anther condition to trigger this bug: the list is not empty and
> no element found (if (iter != opp_dev)).

I suggest reading the code again, considering opp_debug_unregister()
as well.

What's happening here is this:

- Several devices share the OPP table.
- One of them (devX) is going away and opp_debug_unregister() is called for this device.
- If devX is the last device for this OPP table, then we don't migrate
and just release all resources.
- Otherwise, we migrate it to the next element in the list. i.e. any
device which != devX.

Please tell based on this where do you see a possibility of a bug.
Surely there can be one, but I fail to see it at the moment and need
more detail of the same.

Thanks Xiaomeng.

--
viresh

2022-03-28 22:33:02

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH] opp: fix a missing check on list iterator

On 28-03-22, 15:43, Xiaomeng Tong wrote:
> No. the conditon to call opp_migrate_dentry(opp_dev, opp_table); is:
> if (!list_is_singular(&opp_table->dev_list)),
>
> while list_is_singlular is: !list_empty(head) && (head->next == head->prev);
>
> so the condition is: list_empty(head) || (head->next != head->prev)
>
> if the list is empty, the bug can be triggered.

List can't be empty here by design. It will be a huge bug in that
case, which should lead to crash somewhere.

--
viresh

2022-03-31 04:58:57

by Xiaomeng Tong

[permalink] [raw]
Subject: Re: [PATCH] opp: fix a missing check on list iterator

On Mon, 28 Mar 2022 15:09:33 +0530, Viresh Kumar wrote:
> On 28-03-22, 17:13, Xiaomeng Tong wrote:
> > On Mon, 28 Mar 2022 14:20:57 +0530, Viresh Kumar wrote:
> > > On 28-03-22, 15:43, Xiaomeng Tong wrote:
> > > > No. the conditon to call opp_migrate_dentry(opp_dev, opp_table); is:
> > > > if (!list_is_singular(&opp_table->dev_list)),
> > > >
> > > > while list_is_singlular is: !list_empty(head) && (head->next == head->prev);
> > > >
> > > > so the condition is: list_empty(head) || (head->next != head->prev)
> > > >
> > > > if the list is empty, the bug can be triggered.
> > >
> > > List can't be empty here by design. It will be a huge bug in that
> > > case, which should lead to crash somewhere.
> > >
> >
> > There is anther condition to trigger this bug: the list is not empty and
> > no element found (if (iter != opp_dev)).
>
> I suggest reading the code again, considering opp_debug_unregister()
> as well.
>
> What's happening here is this:
>
> - Several devices share the OPP table.
> - One of them (devX) is going away and opp_debug_unregister() is called for this device.
> - If devX is the last device for this OPP table, then we don't migrate
> and just release all resources.
> - Otherwise, we migrate it to the next element in the list. i.e. any
> device which != devX.
>
> Please tell based on this where do you see a possibility of a bug.
> Surely there can be one, but I fail to see it at the moment and need
> more detail of the same.
>

Perhaps you are right. Anyway, It is a good choise to use list iterator
only inside the loop as linus suggested [1], to avoid potential risk.
I have also repost another patch with changed commit message. Please
check it, thank you.

[1]:https://lore.kernel.org/lkml/[email protected]/

--
Xiaomeng Tong