From: sashiko-bot@kernel.org
To: "Zihan Xi" <zihanx@nebusec.ai>, linux-can@vger.kernel.org
Cc: "Marc Kleine-Budde" <mkl@pengutronix.de>,
"Oliver Hartkopp" <socketcan@hartkopp.net>,
"Vincent Mailhol" <mailhol@kernel.org>,
"Oleksij Rempel" <o.rempel@pengutronix.de>
Subject: Re: [PATCH 1/1] can: j1939: fix session reference leak on TX timer restart
Date: Mon, 26 Oct 2026 08:57:44 +0000 [thread overview]
Message-ID: <sashiko-outbox-184711@kernel.org> (raw)
In-Reply-To: <6bbcca411ac9433ae4ed8b8e64f56ff16dfc20b0.1792979091.git.zihanx@nebusec.ai>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] can: j1939: session refcount underflow in j1939_tp_schedule_txtimer()
- [High] can: j1939: race between j1939_tp_schedule_txtimer() and txtimer cancellation
--
commit 132d2449245f60a20ddade9b51a9c2efc0a02136
Author: Zihan Xi <zihanx@nebusec.ai>
can: j1939: fix session reference leak on TX timer restart
This patch adds a spinlock to serialize TX timer rearming in
j1939_tp_schedule_txtimer() and reuses the existing session reference when
restarting an already queued TX timer.
Link: https://bugtracker.nebusec.ai/f/11143
> diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c
> index 8fcfd13e5e6f8..e9af8547c69b4 100644
> --- a/net/can/j1939/transport.c
> +++ b/net/can/j1939/transport.c
> @@ -716,9 +716,17 @@ static int j1939_xtp_tx_abort(struct j1939_priv *priv,
>
> void j1939_tp_schedule_txtimer(struct j1939_session *session, int msec)
> {
> - j1939_session_get(session);
> + int ret;
> +
> + spin_lock_bh(&session->txtimer_lock);
> + /* Keep the timer's reference when only changing its expiry. */
> + ret = hrtimer_try_to_cancel(&session->txtimer);
[Severity: High]
Could temporarily dequeuing session->txtimer here race with callers that
cancel the timer without holding session->txtimer_lock?
In net/can/j1939/transport.c, neither j1939_session_txtimer_cancel() nor
j1939_cancel_active_session() acquires session->txtimer_lock:
static void j1939_session_txtimer_cancel(struct j1939_session *session)
{
if (hrtimer_cancel(&session->txtimer))
j1939_session_put(session);
}
and in j1939_cancel_active_session():
if (hrtimer_try_to_cancel(&session->txtimer) == 1)
j1939_session_put(session);
Previously, j1939_tp_schedule_txtimer() called hrtimer_start() directly on
an already queued timer, updating its expiry under the hrtimer base lock
without transitioning hrtimer_active(&session->txtimer) to false.
With this change, when session->txtimer is queued and its callback is not
running, hrtimer_try_to_cancel(&session->txtimer) dequeues the timer and
returns 1, making hrtimer_active(&session->txtimer) false until
hrtimer_start() runs.
If another CPU calls j1939_session_txtimer_cancel() (for example via
j1939_session_timers_cancel() on session abort or completion, or from
j1939_xtp_rx_cts_one()) or j1939_cancel_active_session() during that window,
hrtimer_cancel() / hrtimer_try_to_cancel() will see the timer as inactive
and return 0. j1939_tp_schedule_txtimer() will then call hrtimer_start() and
re-arm session->txtimer on a session whose timers were already cancelled.
> + /* A running callback may already have requeued the timer. */
> + if (ret != 1 && !hrtimer_is_queued(&session->txtimer))
> + j1939_session_get(session);
> hrtimer_start(&session->txtimer, ms_to_ktime(msec),
> HRTIMER_MODE_REL_SOFT);
[Severity: Critical]
What prevents a queued session->txtimer from expiring or being cancelled
between the lockless hrtimer_is_queued() check and hrtimer_start(), causing
a refcount underflow on session->kref?
In kernel/time/hrtimer.c, hrtimer_try_to_cancel() leaves the timer queued
and returns -1 whenever the timer callback is currently running:
if (!hrtimer_callback_running(timer)) {
ret = remove_hrtimer(timer, base, HRTIMER_STATE_INACTIVE);
if (ret)
trace_hrtimer_cancel(timer);
}
Consider the following sequence across two CPUs when j1939_tp_txtimer() is
running on CPU 0 and has already requeued session->txtimer once (holding one
session->kref reference for the running callback and a second reference for
the queued timer):
1. CPU 1 calls j1939_tp_schedule_txtimer() and takes session->txtimer_lock.
Because j1939_tp_txtimer() is still running on CPU 0,
hrtimer_try_to_cancel(&session->txtimer) returns -1 without removing the
queued timer.
2. CPU 1 checks hrtimer_is_queued(&session->txtimer), which observes
timer->is_queued == 1, so CPU 1 skips j1939_session_get(session) intending
to reuse the queued timer's reference.
3. Before CPU 1 enters hrtimer_start() and takes the hrtimer base lock, the
running j1939_tp_txtimer() callback on CPU 0 finishes (dropping its own
reference via j1939_session_put(session)), and the queued timer either:
- immediately expires in __hrtimer_run_queues() / __run_hrtimer() (which
does not take session->txtimer_lock), dequeuing the timer and running a
second j1939_tp_txtimer() that drops the second session->kref reference:
j1939_session_put(session);
return HRTIMER_NORESTART;
- or is cancelled by an unlocked j1939_session_txtimer_cancel() or
j1939_cancel_active_session() on another CPU, which also dequeues the
timer and drops the second session->kref reference.
4. CPU 1 then executes hrtimer_start(&session->txtimer, ...), which enqueues
session->txtimer from the inactive state without having taken a new
session reference.
When that newly started timer subsequently fires in j1939_tp_txtimer() or is
cancelled, j1939_session_put(session) will drop a reference that was never
taken, underflowing session->kref and freeing struct j1939_session while it
is still in use.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1792979091.git.zihanx@nebusec.ai?part=1
next prev parent reply other threads:[~2026-10-26 8:57 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 7:02 [PATCH 0/1] can: j1939: fix session reference leak on TX timer restart Zihan Xi
2026-10-26 7:02 ` [PATCH 1/1] " Zihan Xi
2026-10-26 7:45 ` Marc Kleine-Budde
2026-10-26 10:08 ` zihan xi
2026-10-26 8:57 ` sashiko-bot [this message]
2026-10-26 10:10 ` zihan xi
2026-10-26 10:51 ` Marc Kleine-Budde
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=sashiko-outbox-184711@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-can@vger.kernel.org \
--cc=mailhol@kernel.org \
--cc=mkl@pengutronix.de \
--cc=o.rempel@pengutronix.de \
--cc=sashiko-reviews@lists.linux.dev \
--cc=socketcan@hartkopp.net \
--cc=zihanx@nebusec.ai \
/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