From: Chaosheng Qu <quchaosheng000406@163.com>
To: Robin van der Gracht <robin@protonic.nl>,
Oleksij Rempel <o.rempel@pengutronix.de>,
Oliver Hartkopp <socketcan@hartkopp.net>,
Marc Kleine-Budde <mkl@pengutronix.de>
Cc: kernel@pengutronix.de, linux-can@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH net v8 1/4] can: j1939: serialize cancel and rearm of the receive timer
Date: Wed, 11 Nov 2026 15:21:00 +0800 [thread overview]
Message-ID: <1794300101.11111.1000001@163.com> (raw)
j1939_tp_set_rxtimeout() cancels the receive timer, takes a reference and
starts it again, but its callers do not agree on active_session_list_lock:
the receive handlers call it without the lock, while j1939_session_cancel()
is reached from the transmit timer path and used to call it with the lock
held.
That makes the sequence non-atomic. If one caller starts the timer after
another has cancelled it and before that one starts it again, the second
hrtimer_start() reprograms an already armed timer. hrtimer_start() does
not report that the timer was armed and does not drop the reference the
first caller took, so that reference is never released: the session, and
the netdev_hold() reference it carries, leak.
The lock cannot simply be taken around the existing helper, because
hrtimer_cancel() waits for a running callback and j1939_tp_rxtimer() takes
the same lock. Add j1939_tp_arm_rxtimer_locked(), which uses
hrtimer_try_to_cancel() instead: it never waits, so it is safe under the
lock, and it returns 1 only when the timer was queued, which is the only
case in which the caller's reference has to be dropped.
j1939_tp_set_rxtimeout() now holds the lock around the helper,
j1939_session_cancel() calls the helper instead of hand-rolling
get + hrtimer_start(), and the rx timeout branch of j1939_tp_rxtimer()
does the same, removing a second copy of that sequence.
Every existing caller of j1939_tp_set_rxtimeout() is outside the lock, so
the added acquisition does not recurse.
Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com>
---
net/can/j1939/transport.c | 66 ++++++++++++++++++++++++++++-----------
1 file changed, 48 insertions(+), 18 deletions(-)
diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c
index 8fcfd13e5e6f..fa23260e1833 100644
--- a/net/can/j1939/transport.c
+++ b/net/can/j1939/transport.c
@@ -721,15 +721,42 @@ void j1939_tp_schedule_txtimer(struct j1939_session *session, int msec)
HRTIMER_MODE_REL_SOFT);
}
-static inline void j1939_tp_set_rxtimeout(struct j1939_session *session,
- int msec)
+/*
+ * Rearm the receive timer as one step under active_session_list_lock.
+ *
+ * j1939_tp_set_rxtimeout() is called from receive handlers that do not hold
+ * the session list lock, while j1939_session_cancel() is called from the
+ * transmit timer path. If the cancel and the rearm are not serialized, a
+ * concurrent caller can start the timer after this one cancelled it and
+ * before this one starts it again: hrtimer_start() on an already armed timer
+ * reprograms it without dropping the reference taken by that caller, and the
+ * session leaks together with its netdev reference.
+ *
+ * hrtimer_try_to_cancel() does not wait for a running callback, so it can be
+ * called with the lock held even though j1939_tp_rxtimer() takes the same
+ * lock; a callback that is already running drops its own reference. For the
+ * same reason the return value is only 1 when the timer was queued: when this
+ * runs from j1939_tp_rxtimer() itself, the timer is no longer queued and 0 is
+ * the expected result.
+ */
+static void j1939_tp_arm_rxtimer_locked(struct j1939_session *session, int msec)
{
- j1939_session_rxtimer_cancel(session);
+ if (hrtimer_try_to_cancel(&session->rxtimer) == 1)
+ j1939_session_put(session);
+
j1939_session_get(session);
hrtimer_start(&session->rxtimer, ms_to_ktime(msec),
HRTIMER_MODE_REL_SOFT);
}
+static inline void j1939_tp_set_rxtimeout(struct j1939_session *session,
+ int msec)
+{
+ j1939_session_list_lock(session->priv);
+ j1939_tp_arm_rxtimer_locked(session, msec);
+ j1939_session_list_unlock(session->priv);
+}
+
static int j1939_session_tx_rts(struct j1939_session *session)
{
u8 dat[8];
@@ -1161,18 +1188,29 @@ static void __j1939_session_cancel(struct j1939_session *session,
static void j1939_session_cancel(struct j1939_session *session,
enum j1939_xtp_abort err)
{
+ bool waiting;
+
j1939_session_list_lock(session->priv);
+ waiting = session->state == J1939_SESSION_WAITING_ABORT;
if (session->state >= J1939_SESSION_ACTIVE &&
session->state < J1939_SESSION_WAITING_ABORT) {
- j1939_tp_set_rxtimeout(session, J1939_XTP_ABORT_TIMEOUT_MS);
__j1939_session_cancel(session, err);
+ j1939_tp_arm_rxtimer_locked(session, J1939_XTP_ABORT_TIMEOUT_MS);
}
j1939_session_list_unlock(session->priv);
if (!session->sk)
j1939_sk_errqueue(session, J1939_ERRQUEUE_RX_ABORT);
+
+ /* The abort already went out on an earlier call, so this one armed no
+ * timer. Several callers cancel the timers before calling here, which
+ * would otherwise leave the session on the list with nothing to finish
+ * it off, so deactivate it now.
+ */
+ if (waiting)
+ j1939_session_deactivate_activate_next(session);
}
static enum hrtimer_restart j1939_tp_txtimer(struct hrtimer *hrtimer)
@@ -1279,10 +1317,8 @@ static enum hrtimer_restart j1939_tp_rxtimer(struct hrtimer *hrtimer)
session->state < J1939_SESSION_ACTIVE_MAX) {
netdev_alert(priv->ndev, "%s: 0x%p: rx timeout, send abort\n",
__func__, session);
- j1939_session_get(session);
- hrtimer_start(&session->rxtimer,
- ms_to_ktime(J1939_XTP_ABORT_TIMEOUT_MS),
- HRTIMER_MODE_REL_SOFT);
+ j1939_tp_arm_rxtimer_locked(session,
+ J1939_XTP_ABORT_TIMEOUT_MS);
__j1939_session_cancel(session, J1939_XTP_ABORT_TIMEOUT);
}
j1939_session_list_unlock(session->priv);
@@ -1729,16 +1765,10 @@ static int j1939_xtp_rx_rts_session_active(struct j1939_session *session,
j1939_session_timers_cancel(session);
j1939_session_cancel(session, J1939_XTP_ABORT_BUSY);
- if (session->transmission) {
- j1939_session_deactivate_activate_next(session);
- } else if (session->state == J1939_SESSION_WAITING_ABORT) {
- /* Force deactivation for the receiver.
- * If we rely on the timer starting in j1939_session_cancel,
- * a second RTS call here will cancel that timer and fail
- * to restart it because the state is already WAITING_ABORT.
- */
- j1939_session_deactivate_activate_next(session);
- }
+ /* Both roles: the peer is restarting the transfer, so free the
+ * slot now rather than wait out the abort timeout.
+ */
+ j1939_session_deactivate_activate_next(session);
return -EBUSY;
}
--
2.43.0
reply other threads:[~2026-11-11 7:18 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=1794300101.11111.1000001@163.com \
--to=quchaosheng000406@163.com \
--cc=kernel@pengutronix.de \
--cc=linux-can@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mkl@pengutronix.de \
--cc=o.rempel@pengutronix.de \
--cc=robin@protonic.nl \
--cc=socketcan@hartkopp.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
all inboxes | Powered by JetHome®