ovpn: Fix race condition in ovpn_dellink()
Subsystems (3)
PATCH 1/1 ovpn: Fix race condition in ovpn_dellink()
When ovpn_dellink() is called, it invokes cancel_delayed_work_sync() to stop keepalive_work before freeing the device.
However, ovpn_nl_peer_new_doit() runs without any lock shared with the RTNL path, so keepalive_work can be scheduled after cancel_delayed_work_sync() returns.
The following is a simple race scenario:
cpu0 cpu1
ovpn_dellink(dev)
cancel_delayed_work_sync(keepalive_work)
ovpn_nl_peer_new_doit()
ovpn_nl_peer_modify()
ovpn_peer_keepalive_set()
mod_delayed_work(keepalive_work)To prevent this race condition, cancel_delayed_work_sync() is replaced with disable_delayed_work_sync().
--- drivers/net/ovpn/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/net/ovpn/main.c +1 -1
@@ -212,7 +212,7 @@ static void ovpn_dellink(struct net_device *dev, struct list_head *head) {struct ovpn_priv *ovpn =netdev_priv (dev); -cancel_delayed_work_sync (&ovpn->keepalive_work); +disable_delayed_work_sync (&ovpn->keepalive_work);ovpn_peers_free (ovpn,NULL ,OVPN_DEL_PEER_REASON_TEARDOWN );unregister_netdevice_queue (dev, head); }
Signature · 1 line
2.43.0
Antonio Quartulli
maintainerHi,
Quoted 13 lines · Hyunwoo Kim
When
ovpn_dellink()is called, it invokescancel_delayed_work_sync()to stopkeepalive_workbefore freeing the device.
However,ovpn_nl_peer_new_doit()runs without any lock shared with the RTNL path, sokeepalive_workcan be scheduled aftercancel_delayed_work_sync()returns.The following is a simple race scenario:
cpu0 cpu1
ovpn_dellink(dev)
cancel_delayed_work_sync(keepalive_work)
Quoted 7 lines
ovpn_nl_peer_new_doit() ovpn_nl_peer_modify() ovpn_peer_keepalive_set() mod_delayed_work(keepalive_work)To prevent this race condition,
cancel_delayed_work_sync()is replaced withdisable_delayed_work_sync().
I was about to agree on your fix, however, it seems there is a larger issue here and this patch is just addressing one symptom.
If you are truly able to execute the whole ovpn_nl_peer_new_doit() after cancel_delayed_work_sync() and before the netdev refcounter reaches 0, I think we may get stuck in the "wait for device to be freed..." loop.
That's because the ovpn_peer_new() will acquire a reference to the netdev, preventing it to be fully released.
Sabrina, do you have any thought on this?
It seems as if there is no safe guard against adding peers while destroying the interface.
Regards,
Signature · 2 lines
Antonio Quartulli OpenVPN Inc.
Sabrina Dubroca
reviewerSorry Antonio, things are a bit busy over here, I'm finally getting to this.
Quoted 31 lines · Antonio Quartulli
Hi,
Quoted 13 lines · Hyunwoo Kim
When
ovpn_dellink()is called, it invokescancel_delayed_work_sync()to stopkeepalive_workbefore freeing the device.
However,ovpn_nl_peer_new_doit()runs without any lock shared with the RTNL path, sokeepalive_workcan be scheduled aftercancel_delayed_work_sync()returns.The following is a simple race scenario:
cpu0 cpu1
ovpn_dellink(dev)
cancel_delayed_work_sync(keepalive_work)Quoted 7 lines
ovpn_nl_peer_new_doit() ovpn_nl_peer_modify() ovpn_peer_keepalive_set() mod_delayed_work(keepalive_work)To prevent this race condition,
cancel_delayed_work_sync()is replaced withdisable_delayed_work_sync().I was about to agree on your fix, however, it seems there is a larger issue here and this patch is just addressing one symptom.
If you are truly able to execute the whole
ovpn_nl_peer_new_doit()aftercancel_delayed_work_sync()and before the netdev refcounter reaches 0, I think we may get stuck in the "wait for device to be freed..." loop.
Quoted 7 lines
That's because the
ovpn_peer_new()will acquire a reference to the netdev, preventing it to be fully released.Sabrina, do you have any thought on this?
It seems as if there is no safe guard against adding peers while destroying the interface.
Feel free to add:
drivers/net/ovpn/main.c +4 -0
@@ -214,6 +214,10 @@ static void ovpn_dellink(struct net_device *dev, struct list_head *head)cancel_delayed_work_sync (&ovpn->keepalive_work);ovpn_peers_free (ovpn,NULL ,OVPN_DEL_PEER_REASON_TEARDOWN ); + +pr_warn ("sleeping\n" ); +msleep (10000 ); +unregister_netdevice_queue (dev, head); }
to help reproduce the race, I usually do something like that if it's possible to sleep in that context.
I think you're right, the newly-added peer will miss ovpn_peers_free. Can we simply move cancel_delayed_work_sync + ovpn_peers_free to ndo_uninit and not provide a dellink?
At ndo_uninit, the netdevice is no longer listed so PEER_NEW won't find it, but delaying the release of the reference on the netdev via peer cleanup should still be ok (unregister_netdevice_many_notify calls unlist_netdevice and then ndo_uninit, then we add it to the todo_list and netdev_run_todo processes that list and does netdev_wait_allrefs_any).
Signature · 1 line
Sabrina