Message ID | 2bdc4b63a4caea153f614c1f041f2ac3492044ed.1723011569.git.jchapman@katalix.com (mailing list archive) |
---|---|
State | Accepted |
Commit | c1b2e36b8776a20a1fbdeb9d3be0637c00d671b0 |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | l2tp: misc improvements | expand |
On Wed, Aug 07, 2024 at 07:54:52AM +0100, James Chapman wrote: > syzbot exposes a race where a net used by l2tp is removed while an > existing pppol2tp socket is closed. In l2tp_pre_exit_net, l2tp queues > TUNNEL_DELETE work items to close each tunnel in the net. When these > are run, new SESSION_DELETE work items are queued to delete each > session in the tunnel. This all happens in drain_workqueue. However, > drain_workqueue allows only new work items if they are queued by other > work items which are already in the queue. If pppol2tp_release runs > after drain_workqueue has started, it may queue a SESSION_DELETE work > item, which results in the warning below in drain_workqueue. > > Address this by flushing the workqueue before drain_workqueue such > that all queued TUNNEL_DELETE work items run before drain_workqueue is > started. This will queue SESSION_DELETE work items for each session in > the tunnel, hence pppol2tp_release or other API requests won't queue > SESSION_DELETE requests once drain_workqueue is started. > I am not convinced here. 1) There is a __flush_workqueue() inside drain_workqueue() too. Why calling it again? 2) What prevents new work items be queued right between your __flush_workqueue() and drain_workqueue()? Thanks.
On 11/08/2024 18:54, Cong Wang wrote: > On Wed, Aug 07, 2024 at 07:54:52AM +0100, James Chapman wrote: >> syzbot exposes a race where a net used by l2tp is removed while an >> existing pppol2tp socket is closed. In l2tp_pre_exit_net, l2tp queues >> TUNNEL_DELETE work items to close each tunnel in the net. When these >> are run, new SESSION_DELETE work items are queued to delete each >> session in the tunnel. This all happens in drain_workqueue. However, >> drain_workqueue allows only new work items if they are queued by other >> work items which are already in the queue. If pppol2tp_release runs >> after drain_workqueue has started, it may queue a SESSION_DELETE work >> item, which results in the warning below in drain_workqueue. >> >> Address this by flushing the workqueue before drain_workqueue such >> that all queued TUNNEL_DELETE work items run before drain_workqueue is >> started. This will queue SESSION_DELETE work items for each session in >> the tunnel, hence pppol2tp_release or other API requests won't queue >> SESSION_DELETE requests once drain_workqueue is started. >> > > I am not convinced here. > > 1) There is a __flush_workqueue() inside drain_workqueue() too. Why > calling it again? Once drain_workqueue starts, it considers any new work item queued outside of its processing as an unexpected condition. By doing __flush_workqueue first, we ensure that all TUNNEL_DELETE items are processed, which means that SESSION_DELETE work items are queued for all existing sessions before drain_workqueue starts. Now if an API request is processed to delete a session, l2tp_session_delete will do nothing because session->dead has already been set. > 2) What prevents new work items be queued right between your > __flush_workqueue() and drain_workqueue()? Hmm, to do so, a new tunnel would need to be created and then deleted by API request once drain_workqueue starts. Is it possible to create new sockets in a net once a net's pre_exit starts? I didn't think so, but I'll recheck. Thanks!
On Mon, Aug 12, 2024 at 10:24:11AM +0100, James Chapman wrote: > On 11/08/2024 18:54, Cong Wang wrote: > > On Wed, Aug 07, 2024 at 07:54:52AM +0100, James Chapman wrote: > > > syzbot exposes a race where a net used by l2tp is removed while an > > > existing pppol2tp socket is closed. In l2tp_pre_exit_net, l2tp queues > > > TUNNEL_DELETE work items to close each tunnel in the net. When these > > > are run, new SESSION_DELETE work items are queued to delete each > > > session in the tunnel. This all happens in drain_workqueue. However, > > > drain_workqueue allows only new work items if they are queued by other > > > work items which are already in the queue. If pppol2tp_release runs > > > after drain_workqueue has started, it may queue a SESSION_DELETE work > > > item, which results in the warning below in drain_workqueue. > > > > > > Address this by flushing the workqueue before drain_workqueue such > > > that all queued TUNNEL_DELETE work items run before drain_workqueue is > > > started. This will queue SESSION_DELETE work items for each session in > > > the tunnel, hence pppol2tp_release or other API requests won't queue > > > SESSION_DELETE requests once drain_workqueue is started. > > > > > > > I am not convinced here. > > > > 1) There is a __flush_workqueue() inside drain_workqueue() too. Why > > calling it again? > > Once drain_workqueue starts, it considers any new work item queued outside > of its processing as an unexpected condition. By doing __flush_workqueue > first, we ensure that all TUNNEL_DELETE items are processed, which means > that SESSION_DELETE work items are queued for all existing sessions before > drain_workqueue starts. Now if an API request is processed to delete a > session, l2tp_session_delete will do nothing because session->dead has > already been set. What about the following case? CPU 0 CPU1 test_and_set_bit() __flush_workqueue() queue_work() drain_workqueue() > > > 2) What prevents new work items be queued right between your > > __flush_workqueue() and drain_workqueue()? > > Hmm, to do so, a new tunnel would need to be created and then deleted by API > request once drain_workqueue starts. Is it possible to create new sockets in > a net once a net's pre_exit starts? I didn't think so, but I'll recheck. I don't quite understand why you need to create a new one while tearing down the old one(s). I think what you need here is some way to properly waiting for the socket tear down to finish. Thanks.
diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index 6ef1c80bb3bf..ddcf07d85877 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -1844,8 +1844,16 @@ static __net_exit void l2tp_pre_exit_net(struct net *net) } rcu_read_unlock_bh(); - if (l2tp_wq) + if (l2tp_wq) { + /* ensure that all TUNNEL_DELETE work items are run before + * draining the work queue since TUNNEL_DELETE requests may + * queue SESSION_DELETE work items for each session in the + * tunnel. drain_workqueue may otherwise warn if SESSION_DELETE + * requests are queued while the work queue is being drained. + */ + __flush_workqueue(l2tp_wq); drain_workqueue(l2tp_wq); + } } static __net_exit void l2tp_exit_net(struct net *net)