From: Hyunwoo Kim <imv4bel@gmail.com>
To: Sabrina Dubroca <sd@queasysnail.net>
Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org, nate.karstens@garmin.com,
linux@treblig.org, Julia.Lawall@inria.fr, netdev@vger.kernel.org,
imv4bel@gmail.com
Subject: Re: [PATCH] strparser: Use worker disable API instead of cancellation in strp_done()
Date: Wed, 18 Feb 2026 04:45:33 +0900 [thread overview]
Message-ID: <aZTFXZcPiu8GBAY7@v4bel> (raw)
In-Reply-To: <aZSxxdNWGLGBj1lD@krikkit>
On Tue, Feb 17, 2026 at 07:21:57PM +0100, Sabrina Dubroca wrote:
> 2026-02-16, 18:48:08 +0900, Hyunwoo Kim wrote:
> > When strp_stop() and strp_done() are called without holding lock_sock(),
> > they can race with worker-scheduling paths such as the Delayed ACK handler
> > and ksoftirqd.
> > Specifically, after cancel_delayed_work_sync() and cancel_work_sync() are
> > invoked from strp_done(), the workers may still be scheduled.
> > As a result, the workers may dereference freed objects.
> >
> > To prevent these races, the cancellation APIs are replaced with
> > worker-disabling APIs.
> >
> > Fixes: 829385f08ae9 ("strparser: Use delayed work instead of timer for msg timeout")
>
> That's the correct commit for msg_timer_work, but not for
> strp->work. No race was possible when msg timeout was using a timer?
Of course, the race could also occur when the message timeout was
implemented using a timer.
> Your second scenario relies only on strp->work so I would think yes.
Using Fixes: bbb0302 ("strparser: Generalize strparser") should cover
both cases.
>
> > Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
> > ---
> > net/strparser/strparser.c | 4 ++--
> > 1 file changed, 2 insertions(+), 2 deletions(-)
> >
> > diff --git a/net/strparser/strparser.c b/net/strparser/strparser.c
> > index fe0e76fdd1f1..15cd9cadbd1a 100644
> > --- a/net/strparser/strparser.c
> > +++ b/net/strparser/strparser.c
> > @@ -503,8 +503,8 @@ void strp_done(struct strparser *strp)
> > {
> > WARN_ON(!strp->stopped);
> >
> > - cancel_delayed_work_sync(&strp->msg_timer_work);
> > - cancel_work_sync(&strp->work);
> > + disable_delayed_work_sync(&strp->msg_timer_work);
> > + disable_work_sync(&strp->work);
>
> The change itself looks reasonable.
>
> > if (strp->skb_head) {
> > kfree_skb(strp->skb_head);
> > --
> > 2.43.0
> >
> > ---
> > Dear,
> >
> > The following is a simplified scenario illustrating how each race can occur. Since espintcp_close() does not hold lock_sock(), the race is possible.
> > Although cancel_work_sync(&strp->work) does not appear to be easy to trigger in practice here, it still seems better to fix it as well.
>
> What about the other users of strp? Only espintcp is racy?
Any subsystem that calls strp_stop() and strp_done() outside of
lock_sock() is racy.
>
> If strp_done can run concurrently with __strp_recv, it seems we could
> also end up leaking strp->skb_head, if __strp_recv stores a new one
> after we've cleared the old?
I am not fully sure about this part yet, so I think it should be
discussed separately in another thread.
>
>
> > ```
> > cpu0 cpu1
> >
> > espintcp_close()
> > espintcp_data_ready()
> > if (unlikely(strp->stopped)) return;
> > strp_stop()
> > strp->stopped = 1;
> > strp_done()
> > cancel_delayed_work_sync(&strp->msg_timer_work);
> > strp_data_ready()
>
> In this order, strp_data_ready will see strp->stopped and return
> without doing anything.
>
> (I'm confused by the "if (unlikely(strp->stopped))" above though,
> maybe you meant espintcp_data_ready -> strp_data_ready -> if (...))
Sorry for the confusion. I accidentally mixed up the call order in the
strp_data_ready() path.
More precisely, the scenario is that espintcp_data_ready() → strp_data_ready()
runs first, passes the if (unlikely(strp->stopped)) check, and only after
that strp->stopped = 1 is set.
```
cpu0 cpu1
espintcp_close()
espintcp_data_ready()
strp_data_ready()
if (unlikely(strp->stopped)) return;
strp_stop()
strp->stopped = 1;
strp_done()
cancel_delayed_work_sync(&strp->msg_timer_work);
strp_read_sock()
tcp_read_sock()
__tcp_read_sock()
strp_recv()
__strp_recv()
strp_start_timer()
mod_delayed_work(strp_wq, &strp->msg_timer_work, timeo);
```
```
cpu0 cpu1
espintcp_close()
espintcp_data_ready()
strp_data_ready()
if (unlikely(strp->stopped)) return;
strp_stop()
strp->stopped = 1;
strp_done()
cancel_work_sync(&strp->work);
if (strp_read_sock(strp) == -ENOMEM)
queue_work()
```
>
> > strp_read_sock()
> > tcp_read_sock()
> > __tcp_read_sock()
> > strp_recv()
> > __strp_recv()
> > strp_start_timer()
> > mod_delayed_work(strp_wq, &strp->msg_timer_work, timeo);
> > ```
> > ```
> > cpu0 cpu1
> >
> > espintcp_close()
> > sk->sk_data_ready()
> > espintcp_data_ready()
> > if (unlikely(strp->stopped)) return;
> > strp_stop()
> > strp->stopped = 1;
> > strp_done()
> > cancel_work_sync(&strp->work);
> > if (strp_read_sock(strp) == -ENOMEM)
> > queue_work()
>
> Here the problem would be if we enter do_strp_work after all the
> socket data has already been freed? Otherwise again the test on
> strp->stopped will make do_strp_work return early. (this would be
> unexpected but should be safe)
If the worker is scheduled after the cancel call, then during
espintcp_close() → tcp_close(), the sk will be freed and the ctx
will be freed as well.
As a result, the kworker will access the freed ctx->strp.
This access to the freed ctx happens before the actual worker
handler is called, so the problem occurs regardless of the
condition checks in do_strp_work().
```
worker_thread()
assign_work(work, &worker->scheduled)
move_linked_works(work) // work: ctx->strp->msg_timer_work or ctx->strp->work
```
>
> > ```
> >
> > Best regards,
> > Hyunwoo Kim
>
> --
> Sabrina
next prev parent reply other threads:[~2026-02-17 19:45 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-16 9:48 [PATCH] strparser: Use worker disable API instead of cancellation in strp_done() Hyunwoo Kim
2026-02-17 18:21 ` Sabrina Dubroca
2026-02-17 19:45 ` Hyunwoo Kim [this message]
2026-02-18 18:11 ` Sabrina Dubroca
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aZTFXZcPiu8GBAY7@v4bel \
--to=imv4bel@gmail.com \
--cc=Julia.Lawall@inria.fr \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux@treblig.org \
--cc=nate.karstens@garmin.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sd@queasysnail.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox