[PATCH net 2/2] fsl/fman: Fix refcount handling of fman-related devices

Aleksandr Mishin posted 2 patches 1 month, 1 week ago
[PATCH net 2/2] fsl/fman: Fix refcount handling of fman-related devices
Posted by Aleksandr Mishin 1 month, 1 week ago
In mac_probe() there are multiple calls to of_find_device_by_node(),
fman_bind() and fman_port_bind() which takes references to of_dev->dev.
Not all references taken by these calls are released later on error path
in mac_probe() and in mac_remove() which lead to reference leaks.

Add references release.

Fixes: 3933961682a3 ("fsl/fman: Add FMan MAC driver")
Signed-off-by: Aleksandr Mishin <amishin@t-argos.ru>
---
Compile tested only.

 drivers/net/ethernet/freescale/fman/mac.c | 62 +++++++++++++++++------
 1 file changed, 47 insertions(+), 15 deletions(-)

diff --git a/drivers/net/ethernet/freescale/fman/mac.c b/drivers/net/ethernet/freescale/fman/mac.c
index 9b863db0bf08..11da139082e1 100644
--- a/drivers/net/ethernet/freescale/fman/mac.c
+++ b/drivers/net/ethernet/freescale/fman/mac.c
@@ -204,7 +204,7 @@ static int mac_probe(struct platform_device *_of_dev)
 	if (err) {
 		dev_err(dev, "failed to read cell-index for %pOF\n", dev_node);
 		err = -EINVAL;
-		goto _return_of_node_put;
+		goto _return_dev_put;
 	}
 	/* cell-index 0 => FMan id 1 */
 	fman_id = (u8)(val + 1);
@@ -213,40 +213,51 @@ static int mac_probe(struct platform_device *_of_dev)
 	if (!priv->fman) {
 		dev_err(dev, "fman_bind(%pOF) failed\n", dev_node);
 		err = -ENODEV;
-		goto _return_of_node_put;
+		goto _return_dev_put;
 	}
 
+	/* Two references have been taken in of_find_device_by_node()
+	 * and fman_bind(). Release one of them here. The second one
+	 * will be released in mac_remove().
+	 */
+	put_device(mac_dev->fman_dev);
 	of_node_put(dev_node);
+	dev_node = NULL;
 
 	/* Get the address of the memory mapped registers */
 	mac_dev->res = platform_get_mem_or_io(_of_dev, 0);
 	if (!mac_dev->res) {
 		dev_err(dev, "could not get registers\n");
-		return -EINVAL;
+		err = -EINVAL;
+		goto _return_dev_put;
 	}
 
 	err = devm_request_resource(dev, fman_get_mem_region(priv->fman),
 				    mac_dev->res);
 	if (err) {
 		dev_err_probe(dev, err, "could not request resource\n");
-		return err;
+		goto _return_dev_put;
 	}
 
 	mac_dev->vaddr = devm_ioremap(dev, mac_dev->res->start,
 				      resource_size(mac_dev->res));
 	if (!mac_dev->vaddr) {
 		dev_err(dev, "devm_ioremap() failed\n");
-		return -EIO;
+		err = -EIO;
+		goto _return_dev_put;
 	}
 
-	if (!of_device_is_available(mac_node))
-		return -ENODEV;
+	if (!of_device_is_available(mac_node)) {
+		err = -ENODEV;
+		goto _return_dev_put;
+	}
 
 	/* Get the cell-index */
 	err = of_property_read_u32(mac_node, "cell-index", &val);
 	if (err) {
 		dev_err(dev, "failed to read cell-index for %pOF\n", mac_node);
-		return -EINVAL;
+		err = -EINVAL;
+		goto _return_dev_put;
 	}
 	priv->cell_index = (u8)val;
 
@@ -260,22 +271,26 @@ static int mac_probe(struct platform_device *_of_dev)
 	if (unlikely(nph < 0)) {
 		dev_err(dev, "of_count_phandle_with_args(%pOF, fsl,fman-ports) failed\n",
 			mac_node);
-		return nph;
+		err = nph;
+		goto _return_dev_put;
 	}
 
 	if (nph != ARRAY_SIZE(mac_dev->port)) {
 		dev_err(dev, "Not supported number of fman-ports handles of mac node %pOF from device tree\n",
 			mac_node);
-		return -EINVAL;
+		err = -EINVAL;
+		goto _return_dev_put;
 	}
 
-	for (i = 0; i < ARRAY_SIZE(mac_dev->port); i++) {
+	/* PORT_NUM determines the size of the port array */
+	for (i = 0; i < PORT_NUM; i++) {
 		/* Find the port node */
 		dev_node = of_parse_phandle(mac_node, "fsl,fman-ports", i);
 		if (!dev_node) {
 			dev_err(dev, "of_parse_phandle(%pOF, fsl,fman-ports) failed\n",
 				mac_node);
-			return -EINVAL;
+			err = -EINVAL;
+			goto _return_dev_arr_put;
 		}
 
 		of_dev = of_find_device_by_node(dev_node);
@@ -283,7 +298,7 @@ static int mac_probe(struct platform_device *_of_dev)
 			dev_err(dev, "of_find_device_by_node(%pOF) failed\n",
 				dev_node);
 			err = -EINVAL;
-			goto _return_of_node_put;
+			goto _return_dev_arr_put;
 		}
 		mac_dev->fman_port_devs[i] = &of_dev->dev;
 
@@ -292,9 +307,15 @@ static int mac_probe(struct platform_device *_of_dev)
 			dev_err(dev, "dev_get_drvdata(%pOF) failed\n",
 				dev_node);
 			err = -EINVAL;
-			goto _return_of_node_put;
+			goto _return_dev_arr_put;
 		}
+		/* Two references have been taken in of_find_device_by_node()
+		 * and fman_port_bind(). Release one of them here. The second
+		 * one will be released in mac_remove().
+		 */
+		put_device(mac_dev->fman_port_devs[i]);
 		of_node_put(dev_node);
+		dev_node = NULL;
 	}
 
 	/* Get the PHY connection type */
@@ -314,7 +335,7 @@ static int mac_probe(struct platform_device *_of_dev)
 
 	err = init(mac_dev, mac_node, &params);
 	if (err < 0)
-		return err;
+		goto _return_dev_arr_put;
 
 	if (!is_zero_ether_addr(mac_dev->addr))
 		dev_info(dev, "FMan MAC address: %pM\n", mac_dev->addr);
@@ -329,6 +350,12 @@ static int mac_probe(struct platform_device *_of_dev)
 
 	return err;
 
+_return_dev_arr_put:
+	/* mac_dev is kzalloc'ed */
+	for (i = 0; i < PORT_NUM; i++)
+		put_device(mac_dev->fman_port_devs[i]);
+_return_dev_put:
+	put_device(mac_dev->fman_dev);
 _return_of_node_put:
 	of_node_put(dev_node);
 	return err;
@@ -337,6 +364,11 @@ static int mac_probe(struct platform_device *_of_dev)
 static void mac_remove(struct platform_device *pdev)
 {
 	struct mac_device *mac_dev = platform_get_drvdata(pdev);
+	int		   i;
+
+	for (i = 0; i < PORT_NUM; i++)
+		put_device(mac_dev->fman_port_devs[i]);
+	put_device(mac_dev->fman_dev);
 
 	platform_device_unregister(mac_dev->priv->eth_dev);
 }
-- 
2.30.2
Re: [PATCH net 2/2] fsl/fman: Fix refcount handling of fman-related devices
Posted by Paolo Abeni 1 month, 1 week ago
On 10/15/24 08:01, Aleksandr Mishin wrote:
> In mac_probe() there are multiple calls to of_find_device_by_node(),
> fman_bind() and fman_port_bind() which takes references to of_dev->dev.
> Not all references taken by these calls are released later on error path
> in mac_probe() and in mac_remove() which lead to reference leaks.
> 
> Add references release.
> 
> Fixes: 3933961682a3 ("fsl/fman: Add FMan MAC driver")
> Signed-off-by: Aleksandr Mishin <amishin@t-argos.ru>
> ---
> Compile tested only.
> 
>   drivers/net/ethernet/freescale/fman/mac.c | 62 +++++++++++++++++------
>   1 file changed, 47 insertions(+), 15 deletions(-)
> 
> diff --git a/drivers/net/ethernet/freescale/fman/mac.c b/drivers/net/ethernet/freescale/fman/mac.c
> index 9b863db0bf08..11da139082e1 100644
> --- a/drivers/net/ethernet/freescale/fman/mac.c
> +++ b/drivers/net/ethernet/freescale/fman/mac.c
> @@ -204,7 +204,7 @@ static int mac_probe(struct platform_device *_of_dev)
>   	if (err) {
>   		dev_err(dev, "failed to read cell-index for %pOF\n", dev_node);
>   		err = -EINVAL;
> -		goto _return_of_node_put;
> +		goto _return_dev_put;

We are after a succesful of_find_device_by_node and prior to 
fman_bind(), mac_dev->fman_dev refcount is 1

> @@ -213,40 +213,51 @@ static int mac_probe(struct platform_device *_of_dev)
>   	if (!priv->fman) {
>   		dev_err(dev, "fman_bind(%pOF) failed\n", dev_node);
>   		err = -ENODEV;
> -		goto _return_of_node_put;
> +		goto _return_dev_put;
>   	}
>   
> +	/* Two references have been taken in of_find_device_by_node()
> +	 * and fman_bind(). Release one of them here. The second one
> +	 * will be released in mac_remove().
> +	 */
> +	put_device(mac_dev->fman_dev);
>   	of_node_put(dev_node);
> +	dev_node = NULL;
>   
>   	/* Get the address of the memory mapped registers */
>   	mac_dev->res = platform_get_mem_or_io(_of_dev, 0);
>   	if (!mac_dev->res) {
>   		dev_err(dev, "could not get registers\n");
> -		return -EINVAL;
> +		err = -EINVAL;
> +		goto _return_dev_put;

Here we are after a successful fman_bind(), mac_dev->fman_dev  refcount 
is 2. _return_dev_put will drop a single reference, this error path 
looks buggy.

Similar issue for the _return_dev_arr_put error path below.

Cheers,

Paolo
Re: [PATCH net 2/2] fsl/fman: Fix refcount handling of fman-related devices
Posted by Aleksandr Mishin 1 month, 1 week ago
On 17.10.2024 13:01, Paolo Abeni wrote:
> On 10/15/24 08:01, Aleksandr Mishin wrote:
>> In mac_probe() there are multiple calls to of_find_device_by_node(),
>> fman_bind() and fman_port_bind() which takes references to of_dev->dev.
>> Not all references taken by these calls are released later on error path
>> in mac_probe() and in mac_remove() which lead to reference leaks.
>>
>> Add references release.
>>
>> Fixes: 3933961682a3 ("fsl/fman: Add FMan MAC driver")
>> Signed-off-by: Aleksandr Mishin <amishin@t-argos.ru>
>> ---
>> Compile tested only.
>>
>>   drivers/net/ethernet/freescale/fman/mac.c | 62 +++++++++++++++++------
>>   1 file changed, 47 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/net/ethernet/freescale/fman/mac.c 
>> b/drivers/net/ethernet/freescale/fman/mac.c
>> index 9b863db0bf08..11da139082e1 100644
>> --- a/drivers/net/ethernet/freescale/fman/mac.c
>> +++ b/drivers/net/ethernet/freescale/fman/mac.c
>> @@ -204,7 +204,7 @@ static int mac_probe(struct platform_device 
>> *_of_dev)
>>       if (err) {
>>           dev_err(dev, "failed to read cell-index for %pOF\n", 
>> dev_node);
>>           err = -EINVAL;
>> -        goto _return_of_node_put;
>> +        goto _return_dev_put;
>
> We are after a succesful of_find_device_by_node and prior to 
> fman_bind(), mac_dev->fman_dev refcount is 1


Indeed. refcounts = 1.


>
>> @@ -213,40 +213,51 @@ static int mac_probe(struct platform_device 
>> *_of_dev)
>>       if (!priv->fman) {
>>           dev_err(dev, "fman_bind(%pOF) failed\n", dev_node);
>>           err = -ENODEV;
>> -        goto _return_of_node_put;
>> +        goto _return_dev_put;
>>       }


refcounts: 1 + 1 = 2.


>>   +    /* Two references have been taken in of_find_device_by_node()
>> +     * and fman_bind(). Release one of them here. The second one
>> +     * will be released in mac_remove().
>> +     */
>> +    put_device(mac_dev->fman_dev);


refcounts: 2 - 1 = 1.


>>       of_node_put(dev_node);
>> +    dev_node = NULL;
>>         /* Get the address of the memory mapped registers */
>>       mac_dev->res = platform_get_mem_or_io(_of_dev, 0);
>>       if (!mac_dev->res) {
>>           dev_err(dev, "could not get registers\n");
>> -        return -EINVAL;
>> +        err = -EINVAL;
>> +        goto _return_dev_put;
>
> Here we are after a successful fman_bind(), mac_dev->fman_dev refcount 
> is 2. _return_dev_put will drop a single reference, this error path 
> looks buggy.


We released 1 reference above with "put_device(mac_dev->fman_dev);".


>
> Similar issue for the _return_dev_arr_put error path below.


Similar situation: we release 1 reference with 
"put_device(mac_dev->fman_port_devs[i]);".


>
> Cheers,
>
> Paolo
>
-- 
Kind regards
Aleksandr