[PATCH v2] net: eth-uclass: revalidate priv after stop() in eth_halt()

In eth_halt(), reread and revalidate priv after calling stop(), as it may have been freed, leaving a dangling pointer.
In the ethernet gadget implementation, the gadget device gets probed during start() and removed during stop(), which includes freeing `uclass_priv_` to which `priv` is pointing. Writing to `priv` after stop() may corrupt the `fd` member of `struct malloc_chunk`, which represents the freed block, and could cause hard-to-debug crashes on subsequent calls to malloc()/free().
Signed-off-by: Niel Fourie lusus@denx.de Cc: Ramon Fried rfried.dev@gmail.com Cc: Marek Vasut marex@denx.de Cc: Lukasz Majewski lukma@denx.de --- Changes for v2: - Revalidate priv instead of changing state before stop() - Added explanational comment
This patch my be dropped if the patch which addresses the root cause ("usb: gadget: ether: split start/stop from init/halt") is accepted.
net/eth-uclass.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/net/eth-uclass.c b/net/eth-uclass.c index f41da4b37b3..7d5783b5cab 100644 --- a/net/eth-uclass.c +++ b/net/eth-uclass.c @@ -341,8 +341,11 @@ void eth_halt(void) priv = dev_get_uclass_priv(current); if (!priv || !priv->running) return; - eth_get_ops(current)->stop(current); + /* Ethernet gadget frees priv during stop, workaround until fixed... */ + priv = dev_get_uclass_priv(current); + if (!priv || !priv->running) + return; priv->state = ETH_STATE_PASSIVE; priv->running = false; }

Split out _usb_eth_start() from _usb_eth_init() and usb_eth_stop() from _usb_eth_halt(). Now _usb_eth_init() only initialises and registers the gadget device, which _usb_eth_halt() reverses, and together are used for probing and removing the device. The _usb_eth_start() and _usb_eth_stop() functions connect and disconnect the gadget as expected by the start()/stop() callbacks.
Previously the gadget device was probed on every start() and removed on every stop(), which is inconsistent with other DM_ETH drivers. For non-DM gadget drivers the old behaviour has been retained.
Signed-off-by: Niel Fourie lusus@denx.de Cc: Marek Vasut marex@denx.de Cc: Lukasz Majewski lukma@denx.de Cc: Ramon Fried rfried.dev@gmail.com --- drivers/usb/gadget/ether.c | 58 +++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-)
diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 43aec7ffa70..a75b4eeb5bb 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -2305,15 +2305,11 @@ fail:
/*-------------------------------------------------------------------------*/ static void _usb_eth_halt(struct ether_priv *priv); +static void _usb_eth_stop(struct ether_priv *priv);
static int _usb_eth_init(struct ether_priv *priv) { - struct eth_dev *dev = &priv->ethdev; - struct usb_gadget *gadget; - unsigned long ts; int ret; - unsigned long timeout = USB_CONNECT_TIMEOUT; - ret = usb_gadget_initialize(0); if (ret) return ret; @@ -2353,14 +2349,26 @@ static int _usb_eth_init(struct ether_priv *priv) priv->eth_driver.resume = eth_resume; if (usb_gadget_register_driver(&priv->eth_driver) < 0) goto fail; + return 0; +fail: + _usb_eth_halt(priv); + return -1; +}
- dev->network_started = 0; +static int _usb_eth_start(struct ether_priv *priv) +{ + unsigned long ts; + unsigned long timeout = USB_CONNECT_TIMEOUT; + struct eth_dev *dev = &priv->ethdev; + + if (!dev->gadget) + return -1;
+ dev->network_started = 0; packet_received = 0; packet_sent = 0;
- gadget = dev->gadget; - usb_gadget_connect(gadget); + usb_gadget_connect(dev->gadget);
if (env_get("cdc_connect_timeout")) timeout = dectoul(env_get("cdc_connect_timeout"), NULL) * CONFIG_SYS_HZ; @@ -2378,6 +2386,7 @@ static int _usb_eth_init(struct ether_priv *priv) rx_submit(dev, dev->rx_req, 0); return 0; fail: + _usb_eth_stop(priv); _usb_eth_halt(priv); return -1; } @@ -2457,11 +2466,10 @@ static int _usb_eth_recv(struct ether_priv *priv) return 0; }
-static void _usb_eth_halt(struct ether_priv *priv) +static void _usb_eth_stop(struct ether_priv *priv) { struct eth_dev *dev = &priv->ethdev;
- /* If the gadget not registered, simple return */ if (!dev->gadget) return;
@@ -2485,6 +2493,14 @@ static void _usb_eth_halt(struct ether_priv *priv) usb_gadget_handle_interrupts(0); dev->network_started = 0; } +} + +static void _usb_eth_halt(struct ether_priv *priv) +{ + struct eth_dev *dev = &priv->ethdev; + + if (!dev->gadget) + return;
usb_gadget_unregister_driver(&priv->eth_driver); usb_gadget_release(0); @@ -2493,9 +2509,13 @@ static void _usb_eth_halt(struct ether_priv *priv) #ifndef CONFIG_DM_ETH static int usb_eth_init(struct eth_device *netdev, struct bd_info *bd) { + int ret; struct ether_priv *priv = (struct ether_priv *)netdev->priv;
- return _usb_eth_init(priv); + ret = _usb_eth_init(priv); + if (!ret) + ret = _usb_eth_start(priv); + return ret; }
static int usb_eth_send(struct eth_device *netdev, void *packet, int length) @@ -2536,6 +2556,7 @@ void usb_eth_halt(struct eth_device *netdev) { struct ether_priv *priv = (struct ether_priv *)netdev->priv;
+ _usb_eth_stop(priv); _usb_eth_halt(priv); }
@@ -2559,7 +2580,7 @@ static int usb_eth_start(struct udevice *dev) { struct ether_priv *priv = dev_get_priv(dev);
- return _usb_eth_init(priv); + return _usb_eth_start(priv); }
static int usb_eth_send(struct udevice *dev, void *packet, int length) @@ -2609,7 +2630,7 @@ static void usb_eth_stop(struct udevice *dev) { struct ether_priv *priv = dev_get_priv(dev);
- _usb_eth_halt(priv); + _usb_eth_stop(priv); }
static int usb_eth_probe(struct udevice *dev) @@ -2623,6 +2644,16 @@ static int usb_eth_probe(struct udevice *dev) get_ether_addr(CONFIG_USBNET_DEV_ADDR, pdata->enetaddr); eth_env_set_enetaddr("usbnet_devaddr", pdata->enetaddr);
+ return _usb_eth_init(priv); + + return 0; +} + +static int usb_eth_remove(struct udevice *dev) +{ + struct ether_priv *priv = dev_get_priv(dev); + + _usb_eth_halt(priv); return 0; }
@@ -2658,6 +2689,7 @@ U_BOOT_DRIVER(eth_usb) = { .name = "usb_ether", .id = UCLASS_ETH, .probe = usb_eth_probe, + .remove = usb_eth_remove, .ops = &usb_eth_ops, .priv_auto = sizeof(struct ether_priv), .plat_auto = sizeof(struct eth_pdata),

On 12/12/22 16:29, Niel Fourie wrote:
Split out _usb_eth_start() from _usb_eth_init() and usb_eth_stop() from _usb_eth_halt(). Now _usb_eth_init() only initialises and registers the gadget device, which _usb_eth_halt() reverses, and together are used for probing and removing the device. The _usb_eth_start() and _usb_eth_stop() functions connect and disconnect the gadget as expected by the start()/stop() callbacks.
Previously the gadget device was probed on every start() and removed on every stop(), which is inconsistent with other DM_ETH drivers. For non-DM gadget drivers the old behaviour has been retained.
Does this mean the udevice pointer and associated private date are retained during the entire operation of the USB gadget , i.e. even between stop/start cycles ?

Hi Marek,
On 12/12/2022 17:46, Marek Vasut wrote:
On 12/12/22 16:29, Niel Fourie wrote:
Split out _usb_eth_start() from _usb_eth_init() and usb_eth_stop() from _usb_eth_halt(). Now _usb_eth_init() only initialises and registers the gadget device, which _usb_eth_halt() reverses, and together are used for probing and removing the device. The _usb_eth_start() and _usb_eth_stop() functions connect and disconnect the gadget as expected by the start()/stop() callbacks.
Previously the gadget device was probed on every start() and removed on every stop(), which is inconsistent with other DM_ETH drivers. For non-DM gadget drivers the old behaviour has been retained.
Does this mean the udevice pointer and associated private date are retained during the entire operation of the USB gadget , i.e. even between stop/start cycles ?
In the DM_ETH case, yes. The drivers/devices remain registered the whole time between _usb_eth_init() and _usb_eth_halt(), and the need for revalidating the private data pointer falls away as was done in my previous patch.
I tested this on imx8mp with the dwc3 gadget driver, and the data structures, specifically the private data, remained intact from probe() until remove(). I also tested that probing again after removal works as expected.
I tested the non-DM_ETH case on an iMX6 Phytec Mira (pcm058) which uses the ci_udc.c driver, which appears to not support DM_ETH yet. In that case the gadget drivers get registered/unregistered in usb_eth_init()/usb_eth_halt().
The legacy non-DM_ETH implementation uses init()/halt() similarly to start()/stop() in the DM_ETH implementation, but had no further equivalent for probe()/remove() in struct eth_device. When the Ethernet gadget was ported to DM_ETH, this difference most likely lead to the existing implementation.
I unfortunately did not have other devices on hand to test against, so I am hoping that there are no other surprises.
Best regards, Niel Fourie

On 12/13/22 12:01, Niel Fourie wrote:
Hi Marek,
Hi,
On 12/12/2022 17:46, Marek Vasut wrote:
On 12/12/22 16:29, Niel Fourie wrote:
Split out _usb_eth_start() from _usb_eth_init() and usb_eth_stop() from _usb_eth_halt(). Now _usb_eth_init() only initialises and registers the gadget device, which _usb_eth_halt() reverses, and together are used for probing and removing the device. The _usb_eth_start() and _usb_eth_stop() functions connect and disconnect the gadget as expected by the start()/stop() callbacks.
Previously the gadget device was probed on every start() and removed on every stop(), which is inconsistent with other DM_ETH drivers. For non-DM gadget drivers the old behaviour has been retained.
Does this mean the udevice pointer and associated private date are retained during the entire operation of the USB gadget , i.e. even between stop/start cycles ?
In the DM_ETH case, yes. The drivers/devices remain registered the whole time between _usb_eth_init() and _usb_eth_halt(), and the need for revalidating the private data pointer falls away as was done in my previous patch.
Perfect.
I tested this on imx8mp with the dwc3 gadget driver, and the data structures, specifically the private data, remained intact from probe() until remove(). I also tested that probing again after removal works as expected.
Excellent.
[...]
Should "[PATCH v2] net: eth-uclass: revalidate priv after stop() in eth_halt()" be dropped ? It seems this patch fully replaces it.

Hi Marek,
On 2022-12-15 06:58, Marek Vasut wrote:
On 12/13/22 12:01, Niel Fourie wrote:
Hi Marek,
Hi,
On 12/12/2022 17:46, Marek Vasut wrote:
On 12/12/22 16:29, Niel Fourie wrote:
Split out _usb_eth_start() from _usb_eth_init() and usb_eth_stop() from _usb_eth_halt(). Now _usb_eth_init() only initialises and registers the gadget device, which _usb_eth_halt() reverses, and together are used for probing and removing the device. The _usb_eth_start() and _usb_eth_stop() functions connect and disconnect the gadget as expected by the start()/stop() callbacks.
Previously the gadget device was probed on every start() and removed on every stop(), which is inconsistent with other DM_ETH drivers. For non-DM gadget drivers the old behaviour has been retained.
Does this mean the udevice pointer and associated private date are retained during the entire operation of the USB gadget , i.e. even between stop/start cycles ?
In the DM_ETH case, yes. The drivers/devices remain registered the whole time between _usb_eth_init() and _usb_eth_halt(), and the need for revalidating the private data pointer falls away as was done in my previous patch.
Perfect.
I tested this on imx8mp with the dwc3 gadget driver, and the data structures, specifically the private data, remained intact from probe() until remove(). I also tested that probing again after removal works as expected.
Excellent.
[...]
Should "[PATCH v2] net: eth-uclass: revalidate priv after stop() in eth_halt()" be dropped ? It seems this patch fully replaces it.
That old patch only exists for in case there were showstopper issues with the new patch which I missed, or as a stopgap if major changes were needed first. I did my best to test the new patch on the hardware I have, but if any other gadget drivers were to misbehave, I would not know about it, for example. But if you are happy with this new patch, that old patch could gladly be dropped.
Best regards, Niel Fourie

On 12/16/22 17:35, lusus@denx.de wrote:
Hi Marek,
Hi,
[...]
Should "[PATCH v2] net: eth-uclass: revalidate priv after stop() in eth_halt()" be dropped ? It seems this patch fully replaces it.
That old patch only exists for in case there were showstopper issues with the new patch which I missed, or as a stopgap if major changes were needed first. I did my best to test the new patch on the hardware I have, but if any other gadget drivers were to misbehave, I would not know about it, for example. But if you are happy with this new patch, that old patch could gladly be dropped.
The new patch is by far preferable. If you can sort out the network_started comment, I would like to pick this via usb/next.
Thanks

Hi Marek
On 18/12/2022 02:51, Marek Vasut wrote:
On 12/16/22 17:35, lusus@denx.de wrote:
Hi Marek,
Hi,
[...]
Should "[PATCH v2] net: eth-uclass: revalidate priv after stop() in eth_halt()" be dropped ? It seems this patch fully replaces it.
That old patch only exists for in case there were showstopper issues with the new patch which I missed, or as a stopgap if major changes were needed first. I did my best to test the new patch on the hardware I have, but if any other gadget drivers were to misbehave, I would not know about it, for example. But if you are happy with this new patch, that old patch could gladly be dropped.
The new patch is by far preferable. If you can sort out the network_started comment, I would like to pick this via usb/next.
I did some more testing and I found that keeping network_started as it is works best, so I did not change it for v2 of this patch.
Unfortunately during local testing (on imx8mp), I uncovered an issue in the dwc3 gadget driver causing it to hang. I have a workaround, but I am still looking into fixing the root cause, and that would have to be fixed in a separate patch series.
Similar issues might also still crop up with other gadget drivers.
Best regards, Niel Fourie

On 1/20/23 18:26, Niel Fourie wrote:
Hi Marek
Hi,
On 18/12/2022 02:51, Marek Vasut wrote:
On 12/16/22 17:35, lusus@denx.de wrote:
Hi Marek,
Hi,
[...]
Should "[PATCH v2] net: eth-uclass: revalidate priv after stop() in eth_halt()" be dropped ? It seems this patch fully replaces it.
That old patch only exists for in case there were showstopper issues with the new patch which I missed, or as a stopgap if major changes were needed first. I did my best to test the new patch on the hardware I have, but if any other gadget drivers were to misbehave, I would not know about it, for example. But if you are happy with this new patch, that old patch could gladly be dropped.
The new patch is by far preferable. If you can sort out the network_started comment, I would like to pick this via usb/next.
I did some more testing and I found that keeping network_started as it is works best, so I did not change it for v2 of this patch.
This doesn't answer the question whether netconsole connection gets broken or not by this change, it think the netconsole connection would get broken and that's not good.
Unfortunately during local testing (on imx8mp), I uncovered an issue in the dwc3 gadget driver causing it to hang. I have a workaround, but I am still looking into fixing the root cause, and that would have to be fixed in a separate patch series.
You can send the workaround as [RFC][PATCH] so it wouldn't get included accidentally and possibly CC Peng Fan from NXP.
Similar issues might also still crop up with other gadget drivers.
[...]

On 12/12/22 16:29, Niel Fourie wrote:
[...]
+static int _usb_eth_start(struct ether_priv *priv) +{
unsigned long ts;
unsigned long timeout = USB_CONNECT_TIMEOUT;
struct eth_dev *dev = &priv->ethdev;
if (!dev->gadget)
return -1;
dev->network_started = 0;
Will this work on systems which already have netconsole active ? I think this would break the netconsole connection, since the network would be reinitialized, won't it ?
I would expect this assignment to be in _init and _stop , not in _start callback.
But I wonder whether the current ethernet uclass interface running code does not make the entire network_started mechanism obsolete. See the patch which you referenced previously in related patch:
fa795f45254 ("net: eth-uclass: avoid running start() twice without stop()")
packet_received = 0; packet_sent = 0;
- gadget = dev->gadget;
- usb_gadget_connect(gadget);
usb_gadget_connect(dev->gadget);
if (env_get("cdc_connect_timeout")) timeout = dectoul(env_get("cdc_connect_timeout"), NULL) * CONFIG_SYS_HZ;
[...]
@@ -2493,9 +2509,13 @@ static void _usb_eth_halt(struct ether_priv *priv) #ifndef CONFIG_DM_ETH static int usb_eth_init(struct eth_device *netdev, struct bd_info *bd) {
- int ret; struct ether_priv *priv = (struct ether_priv *)netdev->priv;
Keep the vars sorted as reverse xmas tree please, i.e. int ret goes below the struct .
[...]

On 12/12/22 16:29, Niel Fourie wrote:
In eth_halt(), reread and revalidate priv after calling stop(), as it may have been freed, leaving a dangling pointer.
In the ethernet gadget implementation, the gadget device gets probed during start() and removed during stop(), which includes freeing `uclass_priv_` to which `priv` is pointing. Writing to `priv` after stop() may corrupt the `fd` member of `struct malloc_chunk`, which represents the freed block, and could cause hard-to-debug crashes on subsequent calls to malloc()/free().
Signed-off-by: Niel Fourie lusus@denx.de Cc: Ramon Fried rfried.dev@gmail.com Cc: Marek Vasut marex@denx.de Cc: Lukasz Majewski lukma@denx.de
Changes for v2:
- Revalidate priv instead of changing state before stop()
- Added explanational comment
This patch my be dropped if the patch which addresses the root cause ("usb: gadget: ether: split start/stop from init/halt") is accepted.
net/eth-uclass.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/net/eth-uclass.c b/net/eth-uclass.c index f41da4b37b3..7d5783b5cab 100644 --- a/net/eth-uclass.c +++ b/net/eth-uclass.c @@ -341,8 +341,11 @@ void eth_halt(void) priv = dev_get_uclass_priv(current); if (!priv || !priv->running) return;
- eth_get_ops(current)->stop(current);
- /* Ethernet gadget frees priv during stop, workaround until fixed... */
- priv = dev_get_uclass_priv(current);
- if (!priv || !priv->running)
return;
Is this fixed by the second patch in this series ? If so, drop this patch.

On 12/12/22 16:29, Niel Fourie wrote:
In eth_halt(), reread and revalidate priv after calling stop(), as it may have been freed, leaving a dangling pointer.
In the ethernet gadget implementation, the gadget device gets probed during start() and removed during stop(), which includes freeing `uclass_priv_` to which `priv` is pointing. Writing to `priv` after stop() may corrupt the `fd` member of `struct malloc_chunk`, which represents the freed block, and could cause hard-to-debug crashes on subsequent calls to malloc()/free().
Signed-off-by: Niel Fourie lusus@denx.de Cc: Ramon Fried rfried.dev@gmail.com Cc: Marek Vasut marex@denx.de Cc: Lukasz Majewski lukma@denx.de
Changes for v2:
- Revalidate priv instead of changing state before stop()
- Added explanational comment
This patch my be dropped if the patch which addresses the root cause ("usb: gadget: ether: split start/stop from init/halt") is accepted.
net/eth-uclass.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/net/eth-uclass.c b/net/eth-uclass.c index f41da4b37b3..7d5783b5cab 100644 --- a/net/eth-uclass.c +++ b/net/eth-uclass.c @@ -341,8 +341,11 @@ void eth_halt(void) priv = dev_get_uclass_priv(current); if (!priv || !priv->running) return;
This shouldn't be here.
eth_get_ops(current)->stop(current);
- /* Ethernet gadget frees priv during stop, workaround until fixed... */
- priv = dev_get_uclass_priv(current);
- if (!priv || !priv->running)
priv->state = ETH_STATE_PASSIVE; priv->running = false; }return;
My understanding of discussion in [PATCH] usb: gadget: ether: split start/stop from init/halt is that this patch is not needed with the aforementioned one applied.
participants (3)
-
lusus@denx.de
-
Marek Vasut
-
Niel Fourie