* [PATCH net v2 0/2] net: phy: xilinx-gmii2rgmii: Fix PHY data ownership and removal
@ 2026-10-07 5:48 Vineeth Karumanchi
2026-10-07 5:48 ` [PATCH net v2 1/2] net: phy: xilinx-gmii2rgmii: Avoid overwriting PHY drvdata Vineeth Karumanchi
2026-10-07 5:48 ` [PATCH net v2 2/2] net: phy: xilinx-gmii2rgmii: Restore PHY driver on remove Vineeth Karumanchi
0 siblings, 2 replies; 5+ messages in thread
From: Vineeth Karumanchi @ 2026-10-07 5:48 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Michal Simek,
Harini Katakam, Florian Fainelli, Kedareswara rao Appana
Cc: netdev, linux-arm-kernel, linux-kernel, vineeth.karumanchi, git
The Xilinx GMII-to-RGMII converter copies the attached PHY driver and
replaces its read_status and set_loopback callbacks. This series addresses
two problems in that arrangement: overwriting driver data belonging to
the external PHY, and leaving phydev->drv pointing at freed converter
memory after converter removal.
Patch 1 retrieves the converter private data from its embedded phy_driver
using container_of_const(), preserving the external PHY's MDIO driver-data
field. It fixes the overwrite introduced by commit 168f7a161608 ("net: phy:
gmii2rgmii: Dont use priv field in phy device").
Patch 2 stores private data on the converter's own MDIO device and adds a
remove callback. It restores the original PHY driver under phydev->lock
only if the converter's copy is still installed, and releases the reference
acquired by of_phy_find_device(). This addresses the stale pointer observed
during PHY state-machine polling after converter-only unbind. The missing
removal cleanup dates back to commit f411a6160bd4 ("net: phy: Add
gmiitorgmii converter support"). Apply the patches in order.
Changes in v2:
- Patch 1: unchanged.
- Patch 2: restore the original PHY driver only if phydev->drv still
points to the converter's copy.
- Patch 2: update the comment and commit message to describe serialization
against PHY callbacks that hold phydev->lock.
Link to v1: https://lore.kernel.org/netdev/20261001074718.3944521-1-vineeth.karumanchi@amd.com/
Vineeth Karumanchi (2):
net: phy: xilinx-gmii2rgmii: Avoid overwriting PHY drvdata
net: phy: xilinx-gmii2rgmii: Restore PHY driver on remove
drivers/net/phy/xilinx_gmii2rgmii.c | 29 +++++++++++++++++++++++++----
1 file changed, 25 insertions(+), 4 deletions(-)
base-commit: 23609bce9e1de525d1d0e73fc68c6e7971d0b49e
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v2 1/2] net: phy: xilinx-gmii2rgmii: Avoid overwriting PHY drvdata
2026-10-07 5:48 [PATCH net v2 0/2] net: phy: xilinx-gmii2rgmii: Fix PHY data ownership and removal Vineeth Karumanchi
@ 2026-10-07 5:48 ` Vineeth Karumanchi
2026-10-11 6:06 ` netdev-bot+sashiko
2026-10-07 5:48 ` [PATCH net v2 2/2] net: phy: xilinx-gmii2rgmii: Restore PHY driver on remove Vineeth Karumanchi
1 sibling, 1 reply; 5+ messages in thread
From: Vineeth Karumanchi @ 2026-10-07 5:48 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Michal Simek,
Harini Katakam, Florian Fainelli, Kedareswara rao Appana
Cc: netdev, linux-arm-kernel, linux-kernel, vineeth.karumanchi, git
The GMII-to-RGMII converter wraps the attached PHY driver by copying
its phy_driver structure and replacing the read_status and set_loopback
callbacks.
To access the converter private data from these callbacks, the driver
currently stores it in the attached PHY's MDIO driver-data field. This
field belongs to the underlying PHY driver and may already contain its
private data. Overwriting it can therefore cause the PHY driver to
retrieve an unexpected pointer and behave incorrectly.
The converter-specific phy_driver is embedded in struct gmii2rgmii and
installed as phydev->drv. Use container_of_const() to retrieve the
enclosing gmii2rgmii structure from phydev->drv instead of using the
PHY's driver-data field.
Update xgmiitorgmii_configure() to accept a const pointer accordingly.
Fixes: 168f7a161608 ("net: phy: gmii2rgmii: Dont use priv field in phy device")
Signed-off-by: Vineeth Karumanchi <vineeth.karumanchi@amd.com>
---
drivers/net/phy/xilinx_gmii2rgmii.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/net/phy/xilinx_gmii2rgmii.c b/drivers/net/phy/xilinx_gmii2rgmii.c
index 2024d8ef36d9..61f71e977a57 100644
--- a/drivers/net/phy/xilinx_gmii2rgmii.c
+++ b/drivers/net/phy/xilinx_gmii2rgmii.c
@@ -28,7 +28,7 @@ struct gmii2rgmii {
struct mdio_device *mdio;
};
-static void xgmiitorgmii_configure(struct gmii2rgmii *priv, int speed)
+static void xgmiitorgmii_configure(const struct gmii2rgmii *priv, int speed)
{
struct mii_bus *bus = priv->mdio->bus;
int addr = priv->mdio->addr;
@@ -49,7 +49,9 @@ static void xgmiitorgmii_configure(struct gmii2rgmii *priv, int speed)
static int xgmiitorgmii_read_status(struct phy_device *phydev)
{
- struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio);
+ const struct gmii2rgmii *priv = container_of_const(phydev->drv,
+ struct gmii2rgmii,
+ conv_phy_drv);
int err;
if (priv->phy_drv->read_status)
@@ -67,7 +69,9 @@ static int xgmiitorgmii_read_status(struct phy_device *phydev)
static int xgmiitorgmii_set_loopback(struct phy_device *phydev, bool enable,
int speed)
{
- struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio);
+ const struct gmii2rgmii *priv = container_of_const(phydev->drv,
+ struct gmii2rgmii,
+ conv_phy_drv);
int err;
if (priv->phy_drv->set_loopback)
@@ -123,7 +127,6 @@ static int xgmiitorgmii_probe(struct mdio_device *mdiodev)
sizeof(struct phy_driver));
priv->conv_phy_drv.read_status = xgmiitorgmii_read_status;
priv->conv_phy_drv.set_loopback = xgmiitorgmii_set_loopback;
- mdiodev_set_drvdata(&priv->phy_dev->mdio, priv);
priv->phy_dev->drv = &priv->conv_phy_drv;
return 0;
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v2 2/2] net: phy: xilinx-gmii2rgmii: Restore PHY driver on remove
2026-10-07 5:48 [PATCH net v2 0/2] net: phy: xilinx-gmii2rgmii: Fix PHY data ownership and removal Vineeth Karumanchi
2026-10-07 5:48 ` [PATCH net v2 1/2] net: phy: xilinx-gmii2rgmii: Avoid overwriting PHY drvdata Vineeth Karumanchi
@ 2026-10-07 5:48 ` Vineeth Karumanchi
2026-10-11 6:06 ` netdev-bot+sashiko
1 sibling, 1 reply; 5+ messages in thread
From: Vineeth Karumanchi @ 2026-10-07 5:48 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Michal Simek,
Harini Katakam, Florian Fainelli, Kedareswara rao Appana
Cc: netdev, linux-arm-kernel, linux-kernel, vineeth.karumanchi, git
The GMII-to-RGMII converter replaces phydev->drv with a modified copy
of the attached PHY driver. This copied driver is embedded in the
converter's private data and is released when the converter is
removed.
Without a remove callback, phydev->drv continues to point to the freed
copy after the converter is unbound. A subsequent PHY operation can
dereference this stale pointer and result in a use-after-free.
With Generic KASAN enabled, unbinding only the converter while the
external PHY remains active produces the following report (abridged):
BUG: KASAN: slab-use-after-free in phy_check_link_status+0x2d8/0x338
Read of size 8 at addr ffff000006c359b0 by task kworker/2:0/27
Workqueue: events_power_efficient phy_state_machine
Call trace:
phy_check_link_status+0x2d8/0x338
_phy_state_machine+0xdc/0xa4c
phy_state_machine+0x2c/0x70
process_one_work+0x554/0xe44
worker_thread+0x6d0/0x1180
kthread+0x2e8/0x5d4
ret_from_fork+0x10/0x20
Allocated by task 55:
...
devm_kmalloc+0xac/0x2ac
xgmiitorgmii_probe+0xa0/0x37c
mdio_probe+0x68/0xb4
...
Freed by task 642:
...
kfree+0x14c/0x38c
release_nodes+0xb4/0x1e0
devres_release_all+0x140/0x1f4
device_unbind_cleanup+0x20/0x190
device_release_driver_internal+0x344/0x460
device_driver_detach+0x3c/0x54
unbind_store+0xe0/0xf8
...
Store the converter private data in its own MDIO device and add a
remove callback. Restore the attached PHY's original driver only if
phydev->drv still points to the converter's copy. Hold phydev->lock to
serialize the restore against PHY callbacks that take that mutex.
Also release the device reference acquired by of_phy_find_device().
Fixes: f411a6160bd4 ("net: phy: Add gmiitorgmii converter support")
Signed-off-by: Vineeth Karumanchi <vineeth.karumanchi@amd.com>
---
Changes in v2:
- Restore the original PHY driver only if phydev->drv still points to
the converter's copy.
- Update the comment and commit message to describe serialization against
PHY callbacks that hold phydev->lock.
Link to v1: https://lore.kernel.org/netdev/20261001074718.3944521-1-vineeth.karumanchi@amd.com/
drivers/net/phy/xilinx_gmii2rgmii.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/drivers/net/phy/xilinx_gmii2rgmii.c b/drivers/net/phy/xilinx_gmii2rgmii.c
index 61f71e977a57..9dcdb91cd441 100644
--- a/drivers/net/phy/xilinx_gmii2rgmii.c
+++ b/drivers/net/phy/xilinx_gmii2rgmii.c
@@ -128,10 +128,27 @@ static int xgmiitorgmii_probe(struct mdio_device *mdiodev)
priv->conv_phy_drv.read_status = xgmiitorgmii_read_status;
priv->conv_phy_drv.set_loopback = xgmiitorgmii_set_loopback;
priv->phy_dev->drv = &priv->conv_phy_drv;
+ mdiodev_set_drvdata(mdiodev, priv);
return 0;
}
+static void xgmiitorgmii_remove(struct mdio_device *mdiodev)
+{
+ struct gmii2rgmii *priv = mdiodev_get_drvdata(mdiodev);
+
+ /*
+ * Restore the original driver only if the converter's copy is still
+ * installed. Serialize against PHY callbacks that hold phydev->lock.
+ */
+ mutex_lock(&priv->phy_dev->lock);
+ if (priv->phy_dev->drv == &priv->conv_phy_drv)
+ priv->phy_dev->drv = priv->phy_drv;
+ mutex_unlock(&priv->phy_dev->lock);
+
+ put_device(&priv->phy_dev->mdio.dev);
+}
+
static const struct of_device_id xgmiitorgmii_of_match[] = {
{ .compatible = "xlnx,gmii-to-rgmii-1.0" },
{},
@@ -140,6 +157,7 @@ MODULE_DEVICE_TABLE(of, xgmiitorgmii_of_match);
static struct mdio_driver xgmiitorgmii_driver = {
.probe = xgmiitorgmii_probe,
+ .remove = xgmiitorgmii_remove,
.mdiodrv.driver = {
.name = "xgmiitorgmii",
.of_match_table = xgmiitorgmii_of_match,
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v2 1/2] net: phy: xilinx-gmii2rgmii: Avoid overwriting PHY drvdata
2026-10-07 5:48 ` [PATCH net v2 1/2] net: phy: xilinx-gmii2rgmii: Avoid overwriting PHY drvdata Vineeth Karumanchi
@ 2026-10-11 6:06 ` netdev-bot+sashiko
0 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 6:06 UTC (permalink / raw)
To: vineeth.karumanchi
Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
michal.simek, harini.katakam, f.fainelli, appanad, netdev,
linux-arm-kernel, linux-kernel, git
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 1 · High: 1 (1) · Medium: 0 (3) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Critical] net: phy: data race and type confusion computing priv from
phydev->drv
- [High] net: phy: TOCTOU race in xgmiitorgmii_read_status yielding
invalid pointer
Pre-existing issues:
- [High] At this commit the converter has no .remove callback.
- [Medium] xgmiitorgmii_configure() stores the int result of
mdiobus_read(bus, addr, XILINX_GMII2RGMII_REG) in a u16 without…
- [Medium] At this commit, the device reference that of_phy_find_device()
takes in xgmiitorgmii_probe() is dropped only on the…
- [Medium] xgmiitorgmii_probe() makes the wrapper visible with a plain
store `priv->phy_dev->drv = &priv->conv_phy_drv`.
--
net: phy: xilinx-gmii2rgmii: Avoid overwriting PHY drvdata
This commit stops the GMII-to-RGMII converter from storing its private
data in the attached PHY's MDIO drvdata field, because that field belongs
to the underlying PHY driver. The read_status and set_loopback wrappers
now get struct gmii2rgmii by calling container_of_const() on phydev->drv.
xgmiitorgmii_configure() now takes a const pointer.
> diff --git a/drivers/net/phy/xilinx_gmii2rgmii.c b/drivers/net/phy/xilinx_gmii2rgmii.c
> index 2024d8ef36d98..61f71e977a574 100644
> --- a/drivers/net/phy/xilinx_gmii2rgmii.c
> +++ b/drivers/net/phy/xilinx_gmii2rgmii.c
> @@ -28,7 +28,7 @@ struct gmii2rgmii {
> struct mdio_device *mdio;
> };
>
> -static void xgmiitorgmii_configure(struct gmii2rgmii *priv, int speed)
> +static void xgmiitorgmii_configure(const struct gmii2rgmii *priv, int speed)
> {
> struct mii_bus *bus = priv->mdio->bus;
> int addr = priv->mdio->addr;
[Severity: Medium]
This isn't a bug introduced by this patch, but this function is being
touched here. What happens if the MDIO read fails?
drivers/net/phy/xilinx_gmii2rgmii.c:xgmiitorgmii_configure() {
...
val = mdiobus_read(bus, addr, XILINX_GMII2RGMII_REG);
val &= ~XILINX_GMII2RGMII_SPEED_MASK;
...
mdiobus_write(bus, addr, XILINX_GMII2RGMII_REG, val);
}
mdiobus_read() returns a negative errno on failure. Since val is a u16,
-EIO becomes 0xfffb.
Only the speed bits are cleared before the write. Wouldn't that set other
bits of the converter register to values the hardware never reported?
The mdiobus_write() result is also ignored. Because the function returns
void, xgmiitorgmii_read_status() and xgmiitorgmii_set_loopback() always
return 0.
Could the PHY layer then believe the converter is set to phydev->speed
when it is not?
> @@ -49,7 +49,9 @@ static void xgmiitorgmii_configure(struct gmii2rgmii *priv, int speed)
>
> static int xgmiitorgmii_read_status(struct phy_device *phydev)
> {
> - struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio);
> + const struct gmii2rgmii *priv = container_of_const(phydev->drv,
> + struct gmii2rgmii,
> + conv_phy_drv);
> int err;
>
> if (priv->phy_drv->read_status)
[Severity: High]
Can a caller that does not hold phydev->lock see phydev->drv change
between selecting this callback and evaluating container_of_const()?
phy_read_status() takes no lock and loads phydev->drv several times:
include/linux/phy.h:phy_read_status() {
...
if (phydev->drv->read_status)
return phydev->drv->read_status(phydev);
...
}
xgmiitorgmii_read_status() then loads phydev->drv once more to compute
priv. phy_state_machine() calls in with phydev->lock held. Some MAC
drivers instead call phy_read_status() directly from their own link
polling or self-test paths, without holding that lock.
Suppose such a caller has already fetched xgmiitorgmii_read_status() as
the function pointer and is preempted. Meanwhile the converter is
unbound through sysfs. The xgmiitorgmii_remove() added by the next
patch in this series restores phydev->drv to priv->phy_drv, and devres
frees priv.
When the caller resumes, container_of_const() is applied to the
original phy_driver rather than to conv_phy_drv. priv is then a pointer
into unrelated memory. priv->phy_drv->read_status is loaded through it
and called.
xgmiitorgmii_remove() takes phydev->lock, but that only serializes
against callers that take the same lock. It does not close this window
for lockless callers.
Is the intent that every phy_read_status() caller must hold
phydev->lock when this converter is in use? If not, how should the
callbacks recover priv safely once phydev->drv can change under a
running callback?
[Severity: High]
This is a pre-existing issue, but is phydev->drv still valid here after
the converter has been unbound?
xgmiitorgmii_probe() allocates priv with devm_kzalloc() on the converter's
own device. It then installs &priv->conv_phy_drv as the attached PHY's
phydev->drv.
The driver has no .remove callback, so nothing restores the original
driver:
static struct mdio_driver xgmiitorgmii_driver = {
.probe = xgmiitorgmii_probe,
...
};
After a sysfs unbind or rmmod of the converter, devres frees priv, but
phydev->drv still points into it.
The next call through phy_state_machine()->phy_read_status() would reach
this function. Here container_of_const() resolves into freed memory, and
priv->phy_drv and priv->mdio are then dereferenced.
A rebind would also memcpy() from the freed copy in xgmiitorgmii_probe().
The next patch in this series, "net: phy: xilinx-gmii2rgmii: Restore PHY
driver on remove", adds xgmiitorgmii_remove(). Under phydev->lock, that
function sets priv->phy_dev->drv back to priv->phy_drv, so this appears
to be fixed there.
[ ... ]
> @@ -123,7 +127,6 @@ static int xgmiitorgmii_probe(struct mdio_device *mdiodev)
> sizeof(struct phy_driver));
> priv->conv_phy_drv.read_status = xgmiitorgmii_read_status;
> priv->conv_phy_drv.set_loopback = xgmiitorgmii_set_loopback;
> - mdiodev_set_drvdata(&priv->phy_dev->mdio, priv);
> priv->phy_dev->drv = &priv->conv_phy_drv;
[Severity: Critical]
Does deriving priv from phydev->drv make the wrapper callbacks depend
on a pointer that nothing synchronizes?
xgmiitorgmii_probe() installs &priv->conv_phy_drv here with a plain
store, without taking phydev->lock. The attached PHY may already be
connected to a MAC with its state machine running. In that case
phydev->drv is rewritten while other code is using it.
With this patch, xgmiitorgmii_read_status() and
xgmiitorgmii_set_loopback() no longer get priv from a field owned by
the converter. They apply container_of_const() to phydev->drv, which
they reload on entry, separately from the load the caller used to pick
the callback.
That is only correct while phydev->drv still equals
&priv->conv_phy_drv. If it has been switched to another phy_driver in
the meantime, the result is that driver's address minus
offsetof(struct gmii2rgmii, conv_phy_drv). The remove path added in
the next patch, which restores the original driver, is one way this
can happen. The resulting pointer is not a struct gmii2rgmii at all.
priv->phy_drv is then read from whatever memory precedes that
phy_driver, and priv->phy_drv->read_status(phydev) becomes an indirect
call through an arbitrary pointer. Wouldn't that crash the kernel, or
let the call be steered somewhere else entirely?
Even within this patch alone, phy_remove() clears phydev->drv when the
attached PHY's own driver is unbound. Could a caller that is already
inside xgmiitorgmii_read_status() then compute priv from NULL?
Could the swap of phydev->drv be done under phydev->lock, with every
path that calls into these callbacks holding it as well? Or could priv
be found in a way that does not depend on the current value of
phydev->drv?
[Severity: Medium]
This isn't a bug introduced by this patch, but is this store ordered
after the setup of the wrapper?
The store to priv->phy_dev->drv is a plain store, done without
phydev->lock and without smp_store_release(). It comes after the stores to
priv->mdio and priv->phy_drv and the memcpy() into conv_phy_drv.
The PHY may already be attached with its state machine running, for
example when the converter module loads after the MAC opened the
interface. On a weakly ordered CPU such as the arm64 Zynq parts, could
another CPU see the new phydev->drv before those earlier stores?
If so, phy_read_status() or phy_loopback() would call
xgmiitorgmii_read_status() or xgmiitorgmii_set_loopback(). Those could
read priv->phy_drv or priv->mdio while they are still NULL from
devm_kzalloc().
>
> return 0;
[Severity: Medium]
This is a pre-existing issue, but does a successful probe leak the
reference on the phy_device?
priv->phy_dev = of_phy_find_device(phy_node);
of_phy_find_device() takes a device reference. That reference is only
dropped on the !priv->phy_dev->drv deferral path.
There is no teardown callback, so nothing calls
put_device(&priv->phy_dev->mdio.dev) after a successful probe. Each
bind/unbind cycle would leak one reference and keep the phy_device from
being freed.
The next patch in this series, "net: phy: xilinx-gmii2rgmii: Restore PHY
driver on remove", appears to fix this by calling put_device() in the new
xgmiitorgmii_remove().
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261007054844.529363-1-vineeth.karumanchi%40amd.com
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v2 2/2] net: phy: xilinx-gmii2rgmii: Restore PHY driver on remove
2026-10-07 5:48 ` [PATCH net v2 2/2] net: phy: xilinx-gmii2rgmii: Restore PHY driver on remove Vineeth Karumanchi
@ 2026-10-11 6:06 ` netdev-bot+sashiko
0 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 6:06 UTC (permalink / raw)
To: vineeth.karumanchi
Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
michal.simek, harini.katakam, f.fainelli, appanad, netdev,
linux-arm-kernel, linux-kernel, git
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 3 · Medium: 1 (1) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The fix only serializes against code that holds phydev->lock, so
the use-after-free it targets is still reachable.
- [High] phy: data race modifying phydev->drv without lock in probe
- [High] net: phy: missing phydev->lock in xgmiitorgmii_probe allows data
race
- [Medium] The check-then-restore in xgmiitorgmii_remove() runs under
phydev->lock, but phy_remove()…
Pre-existing issues:
- [Medium] xgmiitorgmii_probe() memcpy()s the PHY driver into
priv->conv_phy_drv and then publishes `priv->phy_dev->drv =…
--
net: phy: xilinx-gmii2rgmii: Restore PHY driver on remove
This adds a remove callback to the GMII-to-RGMII converter driver. The
callback restores the attached PHY's original phydev->drv under
phydev->lock, but only if the converter's embedded copy is still
installed. It also drops the device reference that of_phy_find_device()
took in probe.
> diff --git a/drivers/net/phy/xilinx_gmii2rgmii.c b/drivers/net/phy/xilinx_gmii2rgmii.c
> index 61f71e977a574..9dcdb91cd441f 100644
> --- a/drivers/net/phy/xilinx_gmii2rgmii.c
> +++ b/drivers/net/phy/xilinx_gmii2rgmii.c
> @@ -128,10 +128,27 @@ static int xgmiitorgmii_probe(struct mdio_device *mdiodev)
> priv->conv_phy_drv.read_status = xgmiitorgmii_read_status;
> priv->conv_phy_drv.set_loopback = xgmiitorgmii_set_loopback;
> priv->phy_dev->drv = &priv->conv_phy_drv;
[Severity: Medium]
This is a pre-existing issue and was not introduced by this patch.
xgmiitorgmii_probe() memcpy()s the PHY driver into priv->conv_phy_drv.
It then publishes the pointer with a plain store. It doesn't hold
phydev->lock and has no release barrier (smp_store_release() or
rcu_assign_pointer()). The new restore in xgmiitorgmii_remove() does
take the lock.
Probe only requires phydev->drv to be non-NULL, so the PHY may already
be attached with its state machine running.
On a weakly ordered CPU such as arm64 Zynq MPSoC, could a reader see the
new phydev->drv before the copied contents of conv_phy_drv? Could it
then call through an uninitialized function pointer?
Nobody has shown this ordering problem in practice, and the patch
doesn't change this sequence.
> + mdiodev_set_drvdata(mdiodev, priv);
>
> return 0;
> }
>
> +static void xgmiitorgmii_remove(struct mdio_device *mdiodev)
> +{
> + struct gmii2rgmii *priv = mdiodev_get_drvdata(mdiodev);
> +
> + /*
> + * Restore the original driver only if the converter's copy is still
> + * installed. Serialize against PHY callbacks that hold phydev->lock.
> + */
> + mutex_lock(&priv->phy_dev->lock);
> + if (priv->phy_dev->drv == &priv->conv_phy_drv)
> + priv->phy_dev->drv = priv->phy_drv;
[Severity: High]
Separately from mutual exclusion, the unlocked store in
xgmiitorgmii_probe() leaves nothing ordering the construction of the
copy against its publication. Probe memcpy()s the PHY driver into
priv->conv_phy_drv, overrides read_status and set_loopback, and then
assigns &priv->conv_phy_drv to phydev->drv with a plain store. There
is no smp_store_release(), and no mutex_unlock() that would give the
store release semantics.
priv comes from devm_kzalloc(), so conv_phy_drv starts out zeroed. On
a weakly ordered CPU, such as the arm64 Zynq MPSoC parts this IP is
used with, a reader holding phydev->lock on another CPU, for example
phy_read_status() from phy_check_link_status(), could observe the new
phydev->drv before all of the copied fields are visible. It would then
see zeroed or partially copied callbacks and flags for that call,
silently falling back to genphy behaviour, skipping the PHY driver's
hooks, or bypassing xgmiitorgmii_read_status() altogether.
With this patch the realistic trigger is rebinding the converter while
the PHY is attached and running, since remove now makes that sequence
work. Taking phydev->lock across the read of phydev->drv, the memcpy()
and the store in probe would give lock-holding readers the ordering
they need, mirroring what remove does. Readers that load phydev->drv
without the lock would still need something stronger. Is there a
reason probe was left unlocked in v2?
[Severity: High]
The new xgmiitorgmii_remove() takes phydev->lock so that restoring
phydev->drv is serialized against PHY callbacks that hold that mutex.
xgmiitorgmii_probe() writes the same field in the other direction,
replacing priv->phy_dev->drv with &priv->conv_phy_drv, but it does so
without taking the lock.
Before this patch, unbinding the converter left phydev->drv dangling,
so a later rebind wasn't something that could work anyway. Now that
remove puts the original driver back, unbind followed by bind through
sysfs becomes a sequence people will actually use. There is no device
link tying the converter's probe to the PHY's attach state, so on
rebind the PHY can already be attached to an interface that is up,
with phy_state_machine() running on another CPU.
In that case probe changes phydev->drv while phy_check_link_status()
holds phydev->lock and is inside phy_read_status(), which loads
phydev->drv more than once. A single locked call can then test the
callback on one driver and invoke it through another, which is the
kind of mid-call switch the locking in remove exists to prevent.
Should xgmiitorgmii_probe() hold phydev->lock around reading
phydev->drv, the memcpy() into conv_phy_drv and the store, so that both
directions follow the rule described in the new comment in remove?
[Severity: Medium]
Can this check-then-restore race with phy_remove() on the external PHY?
phy_remove() writes phydev->drv without taking phydev->lock:
drivers/net/phy/phy_device.c:phy_remove() {
...
if (phydev->drv && phydev->drv->remove)
phydev->drv->remove(phydev);
...
phydev->drv = NULL;
...
}
The converter and the external PHY are separate mdio_devices.
mdio_bus_type does not set need_parent_lock, so the driver core holds
only each device's own device_lock. Sysfs unbinds of the two devices can
therefore run at the same time.
Consider this interleaving:
converter PHY
xgmiitorgmii_remove()
drv == &priv->conv_phy_drv (true)
phy_remove()
phydev->drv = NULL;
phydev->drv = priv->phy_drv;
The PHY is now unbound, but phydev->drv still points at its old driver.
That pointer dangles once the driver's module is unloaded, and later
if (!phydev->drv) checks in phylib would follow it.
The reverse order has a problem too. phy_remove() can be calling through
the converter copy via phydev->drv->remove while xgmiitorgmii_remove()
restores the pointer and returns, and devres then frees priv. Is that
also a use-after-free?
> + mutex_unlock(&priv->phy_dev->lock);
> +
> + put_device(&priv->phy_dev->mdio.dev);
> +}
[Severity: High]
Does this fully close the use-after-free described in the commit
message? The restore only serializes against code that holds
phydev->lock.
After xgmiitorgmii_remove() returns,
device_unbind_cleanup()->devres_release_all() frees priv, and the
embedded conv_phy_drv goes with it. Nothing waits for phylib code that
loaded phydev->drv without the lock, and there is no RCU or refcount
protecting it.
One example is ip link set dev X down, which goes through phy_stop():
drivers/net/phy/phy.c:phy_stop() {
...
state_work = _phy_state_machine(phydev);
mutex_unlock(&phydev->lock);
_phy_state_machine_post_work(phydev, state_work);
...
}
For PHY_HALTED this returns PHY_STATE_WORK_SUSPEND, so phy_suspend()
runs after the lock has been dropped:
drivers/net/phy/phy_device.c:phy_suspend() {
const struct phy_driver *phydrv = phydev->drv;
...
if (phydev->wol_enabled && !(phydrv->flags & PHY_ALWAYS_CALL_SUSPEND))
return -EBUSY;
...
ret = phydrv->suspend(phydev);
...
}
Suppose the converter is unbound after phy_suspend() loads phydev->drv
but before it uses phydrv. Wouldn't phy_suspend() then read freed slab
memory and make an indirect call through it? phy_detach()->phy_suspend()
has the same window.
Other functions that read phydev->drv without the lock look similar:
- phy_ethtool_get_sset_count() checks get_sset_count, get_strings and
get_stats through phydev->drv before taking phydev->lock
- phy_ethtool_get_wol() reads drv->get_wol before taking the mutex
- phy_init_hw() dereferences drv->soft_reset, config_init and
config_intr without the lock. It is reachable from
phy_attach_direct(), from resume, and from the SIOCSMIIREG
BMCR_RESET path in phy_mii_ioctl()
- phy_remove() reads drv->remove without the lock
RTNL doesn't help here either, because unbinding the converter never
takes it.
With this patch, the long-lived dangling pointer becomes a narrow race
window, but it is still there. Would a full fix need to keep the copy
alive for as long as phylib can use it? Or would every phydev->drv
reader need to synchronize with the converter's removal?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261007054844.529363-1-vineeth.karumanchi%40amd.com
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-11 6:06 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-07 5:48 [PATCH net v2 0/2] net: phy: xilinx-gmii2rgmii: Fix PHY data ownership and removal Vineeth Karumanchi
2026-10-07 5:48 ` [PATCH net v2 1/2] net: phy: xilinx-gmii2rgmii: Avoid overwriting PHY drvdata Vineeth Karumanchi
2026-10-11 6:06 ` netdev-bot+sashiko
2026-10-07 5:48 ` [PATCH net v2 2/2] net: phy: xilinx-gmii2rgmii: Restore PHY driver on remove Vineeth Karumanchi
2026-10-11 6:06 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®