diff mbox series

opp: fix a missing check on list iterator

Message ID 20220327053943.3071-1-xiam0nd.tong@gmail.com (mailing list archive)
State New, archived
Delegated to: viresh kumar
Headers show
Series opp: fix a missing check on list iterator | expand

Commit Message

Xiaomeng Tong March 27, 2022, 5:39 a.m. UTC
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: stable@vger.kernel.org
Fixes: deaa51465105a ("PM / OPP: Add debugfs support")
Signed-off-by: Xiaomeng Tong <xiam0nd.tong@gmail.com>
---
 drivers/opp/debugfs.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

Comments

Viresh Kumar March 28, 2022, 3:17 a.m. UTC | #1
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: stable@vger.kernel.org
> Fixes: deaa51465105a ("PM / OPP: Add debugfs support")
> Signed-off-by: Xiaomeng Tong <xiam0nd.tong@gmail.com>
> ---
>  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.
Xiaomeng Tong March 28, 2022, 7:43 a.m. UTC | #2
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
Viresh Kumar March 28, 2022, 8:50 a.m. UTC | #3
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.
Xiaomeng Tong March 28, 2022, 9:13 a.m. UTC | #4
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
Viresh Kumar March 28, 2022, 9:39 a.m. UTC | #5
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.
Xiaomeng Tong March 31, 2022, 2:10 a.m. UTC | #6
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/20220301075839.4156-1-xiam0nd.tong@gmail.com/

--
Xiaomeng Tong
diff mbox series

Patch

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;