Merge branch 'net-fix-race-of-rtnl_net_lock-dev_net-dev'
Kuniyuki Iwashima says:
====================
net: Fix race of rtnl_net_lock(dev_net(dev)).
Yael Chemla reported that commit 7fb1073300
("net: Hold rtnl_net_lock()
in (un)?register_netdevice_notifier_dev_net().") started to trigger KASAN's
use-after-free splat.
The problem is that dev_net(dev) fetched before rtnl_net_lock() might be
different after rtnl_net_lock().
The patch 2 fixes the issue by checking dev_net(dev) after rtnl_net_lock(),
and the patch 3 fixes the same potential issue that would emerge once RTNL
is removed.
v4: https://lore.kernel.org/20250212064206.18159-1-kuniyu@amazon.com
v3: https://lore.kernel.org/20250211051217.12613-1-kuniyu@amazon.com
v2: https://lore.kernel.org/20250207044251.65421-1-kuniyu@amazon.com
v1: https://lore.kernel.org/20250130232435.43622-1-kuniyu@amazon.com
====================
Link: https://patch.msgid.link/20250217191129.19967-1-kuniyu@amazon.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
a92c322876
3 changed files with 61 additions and 12 deletions
|
@ -297,6 +297,7 @@ static inline int check_net(const struct net *net)
|
|||
}
|
||||
|
||||
void net_drop_ns(void *);
|
||||
void net_passive_dec(struct net *net);
|
||||
|
||||
#else
|
||||
|
||||
|
@ -326,8 +327,18 @@ static inline int check_net(const struct net *net)
|
|||
}
|
||||
|
||||
#define net_drop_ns NULL
|
||||
|
||||
static inline void net_passive_dec(struct net *net)
|
||||
{
|
||||
refcount_dec(&net->passive);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void net_passive_inc(struct net *net)
|
||||
{
|
||||
refcount_inc(&net->passive);
|
||||
}
|
||||
|
||||
/* Returns true if the netns initialization is completed successfully */
|
||||
static inline bool net_initialized(const struct net *net)
|
||||
{
|
||||
|
|
|
@ -2070,6 +2070,42 @@ static void __move_netdevice_notifier_net(struct net *src_net,
|
|||
__register_netdevice_notifier_net(dst_net, nb, true);
|
||||
}
|
||||
|
||||
static void rtnl_net_dev_lock(struct net_device *dev)
|
||||
{
|
||||
bool again;
|
||||
|
||||
do {
|
||||
struct net *net;
|
||||
|
||||
again = false;
|
||||
|
||||
/* netns might be being dismantled. */
|
||||
rcu_read_lock();
|
||||
net = dev_net_rcu(dev);
|
||||
net_passive_inc(net);
|
||||
rcu_read_unlock();
|
||||
|
||||
rtnl_net_lock(net);
|
||||
|
||||
#ifdef CONFIG_NET_NS
|
||||
/* dev might have been moved to another netns. */
|
||||
if (!net_eq(net, rcu_access_pointer(dev->nd_net.net))) {
|
||||
rtnl_net_unlock(net);
|
||||
net_passive_dec(net);
|
||||
again = true;
|
||||
}
|
||||
#endif
|
||||
} while (again);
|
||||
}
|
||||
|
||||
static void rtnl_net_dev_unlock(struct net_device *dev)
|
||||
{
|
||||
struct net *net = dev_net(dev);
|
||||
|
||||
rtnl_net_unlock(net);
|
||||
net_passive_dec(net);
|
||||
}
|
||||
|
||||
int register_netdevice_notifier_dev_net(struct net_device *dev,
|
||||
struct notifier_block *nb,
|
||||
struct netdev_net_notifier *nn)
|
||||
|
@ -2077,6 +2113,11 @@ int register_netdevice_notifier_dev_net(struct net_device *dev,
|
|||
struct net *net = dev_net(dev);
|
||||
int err;
|
||||
|
||||
/* rtnl_net_lock() assumes dev is not yet published by
|
||||
* register_netdevice().
|
||||
*/
|
||||
DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->dev_list));
|
||||
|
||||
rtnl_net_lock(net);
|
||||
err = __register_netdevice_notifier_net(net, nb, false);
|
||||
if (!err) {
|
||||
|
@ -2093,13 +2134,12 @@ int unregister_netdevice_notifier_dev_net(struct net_device *dev,
|
|||
struct notifier_block *nb,
|
||||
struct netdev_net_notifier *nn)
|
||||
{
|
||||
struct net *net = dev_net(dev);
|
||||
int err;
|
||||
|
||||
rtnl_net_lock(net);
|
||||
rtnl_net_dev_lock(dev);
|
||||
list_del(&nn->list);
|
||||
err = __unregister_netdevice_notifier_net(net, nb);
|
||||
rtnl_net_unlock(net);
|
||||
err = __unregister_netdevice_notifier_net(dev_net(dev), nb);
|
||||
rtnl_net_dev_unlock(dev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -11880,11 +11920,9 @@ EXPORT_SYMBOL(unregister_netdevice_many);
|
|||
*/
|
||||
void unregister_netdev(struct net_device *dev)
|
||||
{
|
||||
struct net *net = dev_net(dev);
|
||||
|
||||
rtnl_net_lock(net);
|
||||
rtnl_net_dev_lock(dev);
|
||||
unregister_netdevice(dev);
|
||||
rtnl_net_unlock(net);
|
||||
rtnl_net_dev_unlock(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(unregister_netdev);
|
||||
|
||||
|
|
|
@ -464,7 +464,7 @@ static void net_complete_free(void)
|
|||
|
||||
}
|
||||
|
||||
static void net_free(struct net *net)
|
||||
void net_passive_dec(struct net *net)
|
||||
{
|
||||
if (refcount_dec_and_test(&net->passive)) {
|
||||
kfree(rcu_access_pointer(net->gen));
|
||||
|
@ -482,7 +482,7 @@ void net_drop_ns(void *p)
|
|||
struct net *net = (struct net *)p;
|
||||
|
||||
if (net)
|
||||
net_free(net);
|
||||
net_passive_dec(net);
|
||||
}
|
||||
|
||||
struct net *copy_net_ns(unsigned long flags,
|
||||
|
@ -523,7 +523,7 @@ put_userns:
|
|||
key_remove_domain(net->key_domain);
|
||||
#endif
|
||||
put_user_ns(user_ns);
|
||||
net_free(net);
|
||||
net_passive_dec(net);
|
||||
dec_ucounts:
|
||||
dec_net_namespaces(ucounts);
|
||||
return ERR_PTR(rv);
|
||||
|
@ -672,7 +672,7 @@ static void cleanup_net(struct work_struct *work)
|
|||
key_remove_domain(net->key_domain);
|
||||
#endif
|
||||
put_user_ns(net->user_ns);
|
||||
net_free(net);
|
||||
net_passive_dec(net);
|
||||
}
|
||||
cleanup_net_task = NULL;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue