[PATCH net-next] macvlan: fix use-after-free of macvlan_port in macvlan_fill_info()

Jiayuan Chen posted 1 patch 6 days, 23 hours ago
drivers/net/macvlan.c | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
[PATCH net-next] macvlan: fix use-after-free of macvlan_port in macvlan_fill_info()
Posted by Jiayuan Chen 6 days, 23 hours ago
This issue was reported by our internal syzkaller [1].

rtnl_link_ops->macvlan_fill_info() reads and dereferences
'port = vlan->port'.

However, when we delete the underlying physical device of a
macvlan (e.g., eth0), we traverse all child macvlan devices and call
ndo_uninit -> macvlan_uninit.

Each child goes through ndo_uninit -> macvlan_uninit, which
synchronously kfree()s the shared port once the last child is gone, but
does not clear vlan->port. The child netdevice itself is NOT freed here:
its free_netdev() is deferred to netdev_run_todo(), and a concurrent
rtnl_getlink() additionally holds a reference via netdev_hold(). So the
netdevice stays alive with a now-dangling vlan->port, which
macvlan_fill_info() later dereferences -> use-after-free.

Here, we set vlan->port = NULL after kfree(port), and then add a NULL check
in macvlan_fill_info(). Both kfree(port) and vlan->port = NULL are atomic
under the protection of the rtnl lock, so this is safe.

In macvlan_fill_info(), if port does not exist, we should simply ignore it
and not goto nla_put_failure; otherwise, it would trigger WARN_ON_ONCE(1)
in rtnl_getlink().

vlan->port has no concurrent reader either (all under rtnl_lock, the
lockless xmit path is quiesced before macvlan_uninit()), so no
READ_ONCE()/WRITE_ONCE() is needed.

Before the blamed commit, rtnl_getlink() ran __dev_get_by_index() and
rtnl_fill_ifinfo() entirely under rtnl_lock, so it could not race with
macvlan_uninit().

[1]:
'''
BUG: KASAN: slab-use-after-free in macvlan_fill_info drivers/net/macvlan.c:1740

Call Trace:
 <TASK>
 macvlan_fill_info+0x681/0x750 drivers/net/macvlan.c:1740
 rtnl_link_info_fill net/core/rtnetlink.c:905 [inline]
 rtnl_link_fill net/core/rtnetlink.c:926 [inline]
 rtnl_fill_ifinfo.isra.0+0x2ba7/0x4d70 net/core/rtnetlink.c:2185
 rtnl_getlink+0x9b7/0xfc0 net/core/rtnetlink.c:4280
 rtnetlink_rcv_msg+0x80f/0xd60 net/core/rtnetlink.c:7062
 netlink_rcv_skb+0x15f/0x430 net/netlink/af_netlink.c:2556
 rtnetlink_rcv+0x21/0x40 net/core/rtnetlink.c:7089
 netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
 netlink_unicast+0x6ef/0xb50 net/netlink/af_netlink.c:1345
 netlink_sendmsg+0x8d8/0xd70 net/netlink/af_netlink.c:1900
 sock_sendmsg_nosec net/socket.c:787 [inline]

Allocated by task 5163:
 macvlan_port_create drivers/net/macvlan.c:1260 [inline]
 macvlan_common_newlink+0x45a/0x1ab0 drivers/net/macvlan.c:1498
 macvlan_newlink+0x2a/0x40 drivers/net/macvlan.c:1596
 rtnl_newlink_create net/core/rtnetlink.c:3905 [inline]
 __rtnl_newlink net/core/rtnetlink.c:4036 [inline]
 rtnl_newlink+0xd38/0x2360 net/core/rtnetlink.c:4151
 rtnetlink_rcv_msg+0x80f/0xd60 net/core/rtnetlink.c:7062
 netlink_rcv_skb+0x15f/0x430 net/netlink/af_netlink.c:2556
 rtnetlink_rcv+0x21/0x40 net/core/rtnetlink.c:7089
 netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
 netlink_unicast+0x6ef/0xb50 net/netlink/af_netlink.c:1345
 netlink_sendmsg+0x8d8/0xd70 net/netlink/af_netlink.c:1900
 sock_sendmsg_nosec net/socket.c:787 [inline]

Freed by task 5166:
 macvlan_port_destroy+0x3c5/0x630 drivers/net/macvlan.c:1319
 macvlan_uninit+0xf5/0x140 drivers/net/macvlan.c:977
 unregister_netdevice_many_notify+0x108f/0x2140 net/core/dev.c:12454
 unregister_netdevice_many+0x1e/0x30 net/core/dev.c:12496
 macvlan_device_event+0x477/0x880 drivers/net/macvlan.c:1856
 notifier_call_chain+0xb7/0x330 kernel/notifier.c:85
 raw_notifier_call_chain+0x32/0x50 kernel/notifier.c:453
 call_netdevice_notifiers_info+0xa7/0x100 net/core/dev.c:2249
 call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
 call_netdevice_notifiers net/core/dev.c:2301 [inline]
 unregister_netdevice_many_notify+0xde2/0x2140 net/core/dev.c:12435
 rtnl_delete_link net/core/rtnetlink.c:3593 [inline]
 rtnl_dellink+0x3db/0xaf0 net/core/rtnetlink.c:3635
 rtnetlink_rcv_msg+0x80f/0xd60 net/core/rtnetlink.c:7062
 netlink_rcv_skb+0x15f/0x430 net/netlink/af_netlink.c:2556
 rtnetlink_rcv+0x21/0x40 net/core/rtnetlink.c:7089
 netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
 netlink_unicast+0x6ef/0xb50 net/netlink/af_netlink.c:1345
 netlink_sendmsg+0x8d8/0xd70 net/netlink/af_netlink.c:1900
 sock_sendmsg_nosec net/socket.c:787 [inline]
'''

Fixes: e896e5c0734b ("rtnetlink: do not acquire RTNL in rtnl_getlink() with RTEXT_FILTER_NAME_ONLY")
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
 drivers/net/macvlan.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
index c40fa331836b..4ff3a4de972e 100644
--- a/drivers/net/macvlan.c
+++ b/drivers/net/macvlan.c
@@ -975,6 +975,8 @@ static void macvlan_uninit(struct net_device *dev)
 	port->count -= 1;
 	if (!port->count)
 		macvlan_port_destroy(port->dev);
+
+	vlan->port = NULL;
 }
 
 static void macvlan_dev_get_stats64(struct net_device *dev,
@@ -1736,12 +1738,14 @@ static int macvlan_fill_info(struct sk_buff *skb,
 	}
 	if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, vlan->bc_queue_len_req))
 		goto nla_put_failure;
-	if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN_USED,
-			READ_ONCE(port->bc_queue_len_used)))
-		goto nla_put_failure;
-	if (port->bc_cutoff != 1 &&
-	    nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, port->bc_cutoff))
-		goto nla_put_failure;
+	if (port) {
+		if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN_USED,
+				READ_ONCE(port->bc_queue_len_used)))
+			goto nla_put_failure;
+		if (port->bc_cutoff != 1 &&
+		    nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, port->bc_cutoff))
+			goto nla_put_failure;
+	}
 	return 0;
 
 nla_put_failure:
-- 
2.43.0
Re: [PATCH net-next] macvlan: fix use-after-free of macvlan_port in macvlan_fill_info()
Posted by Kuniyuki Iwashima 6 days, 4 hours ago
From: Jiayuan Chen <jiayuan.chen@linux.dev>
Date: Mon,  1 Jun 2026 14:13:03 +0800
> This issue was reported by our internal syzkaller [1].
> 
> rtnl_link_ops->macvlan_fill_info() reads and dereferences
> 'port = vlan->port'.
> 
> However, when we delete the underlying physical device of a
> macvlan (e.g., eth0), we traverse all child macvlan devices and call
> ndo_uninit -> macvlan_uninit.
> 
> Each child goes through ndo_uninit -> macvlan_uninit, which
> synchronously kfree()s the shared port once the last child is gone, but
> does not clear vlan->port. The child netdevice itself is NOT freed here:
> its free_netdev() is deferred to netdev_run_todo(), and a concurrent
> rtnl_getlink() additionally holds a reference via netdev_hold(). So the
> netdevice stays alive with a now-dangling vlan->port, which
> macvlan_fill_info() later dereferences -> use-after-free.
> 
> Here, we set vlan->port = NULL after kfree(port), and then add a NULL check
> in macvlan_fill_info(). Both kfree(port) and vlan->port = NULL are atomic
> under the protection of the rtnl lock, so this is safe.
> 
> In macvlan_fill_info(), if port does not exist, we should simply ignore it
> and not goto nla_put_failure; otherwise, it would trigger WARN_ON_ONCE(1)
> in rtnl_getlink().

The splat implies the same class of issues in other devices, so
I don't think this macvlan-specific fix is the right approach.

We should give up dumping if dev->reg_state is not NETREG_REGISTERED
after acquiring RTNL, and I think Eric was preparing such a patch ?

https://lore.kernel.org/netdev/CANn89i+UMyx-rKO0ScZkcJYG_vk=_kddcQC+SxbbYSEyUTPgGw@mail.gmail.com/


> 
> vlan->port has no concurrent reader either (all under rtnl_lock, the
> lockless xmit path is quiesced before macvlan_uninit()), so no
> READ_ONCE()/WRITE_ONCE() is needed.
> 
> Before the blamed commit, rtnl_getlink() ran __dev_get_by_index() and
> rtnl_fill_ifinfo() entirely under rtnl_lock, so it could not race with
> macvlan_uninit().
> 
> [1]:
> '''
> BUG: KASAN: slab-use-after-free in macvlan_fill_info drivers/net/macvlan.c:1740
> 
> Call Trace:
>  <TASK>
>  macvlan_fill_info+0x681/0x750 drivers/net/macvlan.c:1740
>  rtnl_link_info_fill net/core/rtnetlink.c:905 [inline]
>  rtnl_link_fill net/core/rtnetlink.c:926 [inline]
>  rtnl_fill_ifinfo.isra.0+0x2ba7/0x4d70 net/core/rtnetlink.c:2185
>  rtnl_getlink+0x9b7/0xfc0 net/core/rtnetlink.c:4280
>  rtnetlink_rcv_msg+0x80f/0xd60 net/core/rtnetlink.c:7062
>  netlink_rcv_skb+0x15f/0x430 net/netlink/af_netlink.c:2556
>  rtnetlink_rcv+0x21/0x40 net/core/rtnetlink.c:7089
>  netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
>  netlink_unicast+0x6ef/0xb50 net/netlink/af_netlink.c:1345
>  netlink_sendmsg+0x8d8/0xd70 net/netlink/af_netlink.c:1900
>  sock_sendmsg_nosec net/socket.c:787 [inline]
> 
> Allocated by task 5163:
>  macvlan_port_create drivers/net/macvlan.c:1260 [inline]
>  macvlan_common_newlink+0x45a/0x1ab0 drivers/net/macvlan.c:1498
>  macvlan_newlink+0x2a/0x40 drivers/net/macvlan.c:1596
>  rtnl_newlink_create net/core/rtnetlink.c:3905 [inline]
>  __rtnl_newlink net/core/rtnetlink.c:4036 [inline]
>  rtnl_newlink+0xd38/0x2360 net/core/rtnetlink.c:4151
>  rtnetlink_rcv_msg+0x80f/0xd60 net/core/rtnetlink.c:7062
>  netlink_rcv_skb+0x15f/0x430 net/netlink/af_netlink.c:2556
>  rtnetlink_rcv+0x21/0x40 net/core/rtnetlink.c:7089
>  netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
>  netlink_unicast+0x6ef/0xb50 net/netlink/af_netlink.c:1345
>  netlink_sendmsg+0x8d8/0xd70 net/netlink/af_netlink.c:1900
>  sock_sendmsg_nosec net/socket.c:787 [inline]
> 
> Freed by task 5166:
>  macvlan_port_destroy+0x3c5/0x630 drivers/net/macvlan.c:1319
>  macvlan_uninit+0xf5/0x140 drivers/net/macvlan.c:977
>  unregister_netdevice_many_notify+0x108f/0x2140 net/core/dev.c:12454
>  unregister_netdevice_many+0x1e/0x30 net/core/dev.c:12496
>  macvlan_device_event+0x477/0x880 drivers/net/macvlan.c:1856
>  notifier_call_chain+0xb7/0x330 kernel/notifier.c:85
>  raw_notifier_call_chain+0x32/0x50 kernel/notifier.c:453
>  call_netdevice_notifiers_info+0xa7/0x100 net/core/dev.c:2249
>  call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
>  call_netdevice_notifiers net/core/dev.c:2301 [inline]
>  unregister_netdevice_many_notify+0xde2/0x2140 net/core/dev.c:12435
>  rtnl_delete_link net/core/rtnetlink.c:3593 [inline]
>  rtnl_dellink+0x3db/0xaf0 net/core/rtnetlink.c:3635
>  rtnetlink_rcv_msg+0x80f/0xd60 net/core/rtnetlink.c:7062
>  netlink_rcv_skb+0x15f/0x430 net/netlink/af_netlink.c:2556
>  rtnetlink_rcv+0x21/0x40 net/core/rtnetlink.c:7089
>  netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
>  netlink_unicast+0x6ef/0xb50 net/netlink/af_netlink.c:1345
>  netlink_sendmsg+0x8d8/0xd70 net/netlink/af_netlink.c:1900
>  sock_sendmsg_nosec net/socket.c:787 [inline]
> '''
> 
> Fixes: e896e5c0734b ("rtnetlink: do not acquire RTNL in rtnl_getlink() with RTEXT_FILTER_NAME_ONLY")
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
> ---
>  drivers/net/macvlan.c | 16 ++++++++++------
>  1 file changed, 10 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
> index c40fa331836b..4ff3a4de972e 100644
> --- a/drivers/net/macvlan.c
> +++ b/drivers/net/macvlan.c
> @@ -975,6 +975,8 @@ static void macvlan_uninit(struct net_device *dev)
>  	port->count -= 1;
>  	if (!port->count)
>  		macvlan_port_destroy(port->dev);
> +
> +	vlan->port = NULL;
>  }
>  
>  static void macvlan_dev_get_stats64(struct net_device *dev,
> @@ -1736,12 +1738,14 @@ static int macvlan_fill_info(struct sk_buff *skb,
>  	}
>  	if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, vlan->bc_queue_len_req))
>  		goto nla_put_failure;
> -	if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN_USED,
> -			READ_ONCE(port->bc_queue_len_used)))
> -		goto nla_put_failure;
> -	if (port->bc_cutoff != 1 &&
> -	    nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, port->bc_cutoff))
> -		goto nla_put_failure;
> +	if (port) {
> +		if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN_USED,
> +				READ_ONCE(port->bc_queue_len_used)))
> +			goto nla_put_failure;
> +		if (port->bc_cutoff != 1 &&
> +		    nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, port->bc_cutoff))
> +			goto nla_put_failure;
> +	}
>  	return 0;
>  
>  nla_put_failure:
> -- 
> 2.43.0
Re: [PATCH net-next] macvlan: fix use-after-free of macvlan_port in macvlan_fill_info()
Posted by Eric Dumazet 5 days, 20 hours ago
On Mon, Jun 1, 2026 at 5:59 PM Kuniyuki Iwashima <kuniyu@google.com> wrote:
>
> From: Jiayuan Chen <jiayuan.chen@linux.dev>
> Date: Mon,  1 Jun 2026 14:13:03 +0800
> > This issue was reported by our internal syzkaller [1].
> >
> > rtnl_link_ops->macvlan_fill_info() reads and dereferences
> > 'port = vlan->port'.
> >
> > However, when we delete the underlying physical device of a
> > macvlan (e.g., eth0), we traverse all child macvlan devices and call
> > ndo_uninit -> macvlan_uninit.
> >
> > Each child goes through ndo_uninit -> macvlan_uninit, which
> > synchronously kfree()s the shared port once the last child is gone, but
> > does not clear vlan->port. The child netdevice itself is NOT freed here:
> > its free_netdev() is deferred to netdev_run_todo(), and a concurrent
> > rtnl_getlink() additionally holds a reference via netdev_hold(). So the
> > netdevice stays alive with a now-dangling vlan->port, which
> > macvlan_fill_info() later dereferences -> use-after-free.
> >
> > Here, we set vlan->port = NULL after kfree(port), and then add a NULL check
> > in macvlan_fill_info(). Both kfree(port) and vlan->port = NULL are atomic
> > under the protection of the rtnl lock, so this is safe.
> >
> > In macvlan_fill_info(), if port does not exist, we should simply ignore it
> > and not goto nla_put_failure; otherwise, it would trigger WARN_ON_ONCE(1)
> > in rtnl_getlink().
>
> The splat implies the same class of issues in other devices, so
> I don't think this macvlan-specific fix is the right approach.
>
> We should give up dumping if dev->reg_state is not NETREG_REGISTERED
> after acquiring RTNL, and I think Eric was preparing such a patch ?
>
> https://lore.kernel.org/netdev/CANn89i+UMyx-rKO0ScZkcJYG_vk=_kddcQC+SxbbYSEyUTPgGw@mail.gmail.com/
>

Indeed!

https://lore.kernel.org/netdev/20260602091319.1753654-1-edumazet@google.com/T/#u

Thanks!