[PATCH RFC] ipvlan: hold reference to lower device to prevent use-after-free
"syzbot" <[email protected]> Thu, 9 Jul 2026 13:44:25 +0000 (UTC)
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
The ipvlan driver does not hold a reference to its lower device (phy_dev),
allowing the lower device to be freed while the ipvlan device is still
alive and being accessed.
If a stacked network device configuration is created (e.g., a veth device
as the lower device, an ipvlan device on top, and an rxe device on top of
ipvlan), the rxe device holds a reference to the ipvlan device. When the
veth device is unregistered, the ipvlan driver receives a NETDEV_UNREGISTER
event and unregisters the ipvlan device. This triggers a NETDEV_UNREGISTER
event for the ipvlan device, which is caught by the rxe driver, queuing
asynchronous work to unregister the rxe device and drop its reference to
the ipvlan device.
During this process, netdev_run_todo() waits for the refcounts of both veth
and ipvlan devices to drop to 1. Since the ipvlan device's refcount is
elevated by the pending rxe unregistration, it is kept alive. However,
because ipvlan does not hold a reference to its lower veth device, the veth
device's refcount drops to 1, and netdev_run_todo() proceeds to free it.
This leaves ipvlan->phy_dev as a dangling pointer.
If an operation (like querying port attributes via ethtool) is performed on
the ipvlan device while it is still alive, it can access the freed lower
device, triggering a use-after-free:
BUG: KASAN: slab-use-after-free in netdev_need_ops_lock
include/net/netdev_lock.h:30 [inline]
BUG: KASAN: slab-use-after-free in netdev_lock_ops
include/net/netdev_lock.h:41 [inline]
BUG: KASAN: slab-use-after-free in __ethtool_get_link_ksettings+0x230/0x250
net/ethtool/ioctl.c:463
Read of size 1 at addr ffff888118fece09 by task kworker/0:2/919
Call Trace:
<TASK>
netdev_need_ops_lock include/net/netdev_lock.h:30 [inline]
netdev_lock_ops include/net/netdev_lock.h:41 [inline]
__ethtool_get_link_ksettings+0x230/0x250 net/ethtool/ioctl.c:463
__ethtool_get_link_ksettings+0x11f/0x250 net/ethtool/ioctl.c:464
ib_get_eth_speed+0x180/0x7f0 drivers/infiniband/core/verbs.c:2052
rxe_query_port+0x93/0x3d0 drivers/infiniband/sw/rxe/rxe_verbs.c:56
__ib_query_port drivers/infiniband/core/device.c:2129 [inline]
ib_query_port+0x16e/0x830 drivers/infiniband/core/device.c:2161
smc_ib_remember_port_attr net/smc/smc_ib.c:364 [inline]
smc_ib_port_event_work+0x147/0x920 net/smc/smc_ib.c:388
...
</TASK>
Fix this by holding a reference to the lower device using a
netdevice_tracker in struct ipvl_dev. The reference is acquired in
ipvlan_init() and released in a new priv_destructor callback
(ipvlan_dev_free). Releasing the reference in priv_destructor guarantees
that the lower device is held until the absolute end of the ipvlan device's
lifetime, after netdev_run_todo() has finished waiting for the ipvlan
device's refcount to drop to 1. This mirrors the behavior of other stacked
devices like macvlan and vlan, and safely covers ipvtap devices as well.
Fixes: 2ad7bf363841 ("ipvlan: Initial check-in of the IPVLAN driver.")
Assisted-by: Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=5fe14f2ff4ccbace9a26
Link: https://syzkaller.appspot.com/ai_job?id=43c4fc37-40e6-4515-8ae6-40bdb52bb303
To: "Andrew Lunn" <[email protected]>
To: "David S. Miller" <[email protected]>
To: "Eric Dumazet" <[email protected]>
To: "Jakub Kicinski" <[email protected]>
To: <[email protected]>
To: "Paolo Abeni" <[email protected]>
Cc: "Dmitry Skorodumov" <[email protected]>
Cc: "Kees Cook" <[email protected]>
Cc: <[email protected]>
Cc: "Stanislav Fomichev" <[email protected]>
---
diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h
index 80f84fc87..13cdad002 100644
--- a/drivers/net/ipvlan/ipvlan.h
+++ b/drivers/net/ipvlan/ipvlan.h
@@ -64,6 +64,7 @@ struct ipvl_dev {
struct list_head pnode;
struct ipvl_port *port;
struct net_device *phy_dev;
+ netdevice_tracker dev_tracker;
struct list_head addrs;
struct ipvl_pcpu_stats __percpu *pcpu_stats;
DECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE);
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index ed46439a9..b1435296a 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -162,6 +162,9 @@ static int ipvlan_init(struct net_device *dev)
}
port = ipvlan_port_get_rtnl(phy_dev);
port->count += 1;
+
+ netdev_hold(phy_dev, &ipvlan->dev_tracker, GFP_KERNEL);
+
return 0;
}
@@ -673,6 +676,13 @@ void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
}
EXPORT_SYMBOL_GPL(ipvlan_link_delete);
+static void ipvlan_dev_free(struct net_device *dev)
+{
+ struct ipvl_dev *ipvlan = netdev_priv(dev);
+
+ netdev_put(ipvlan->phy_dev, &ipvlan->dev_tracker);
+}
+
void ipvlan_link_setup(struct net_device *dev)
{
ether_setup(dev);
@@ -682,6 +692,7 @@ void ipvlan_link_setup(struct net_device *dev)
dev->priv_flags |= IFF_UNICAST_FLT | IFF_NO_QUEUE;
dev->netdev_ops = &ipvlan_netdev_ops;
dev->needs_free_netdev = true;
+ dev->priv_destructor = ipvlan_dev_free;
dev->header_ops = &ipvlan_header_ops;
dev->ethtool_ops = &ipvlan_ethtool_ops;
}
base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].