* [PATCH net v7 0/4] can: j1939: fix the abort timer and bound EDPO
@ 2026-11-08 1:34 Chaosheng Qu
2026-11-08 1:34 ` [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock Chaosheng Qu
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Chaosheng Qu @ 2026-11-08 1:34 UTC (permalink / raw)
To: Robin van der Gracht, Oleksij Rempel, Oliver Hartkopp,
Marc Kleine-Budde
Cc: kernel, linux-can, linux-kernel
v7 of the j1939 EDPO fix. Two findings from the automated review of v6 were
real and both were in the session cancel path, so this is now a series:
patch 1 is the timer fix, patch 2 is the original EDPO bound check.
[1/4] arms the abort timer under the session list lock. Fixes two things
at once: the hrtimer_cancel()/j1939_tp_rxtimer() deadlock, and the
reference leak when a second unparsable frame re-enters the cancel
path after the timers were killed.
[2/4] is the original change: reject an ETP.CM_DPO that names a packet at
or past pkt.total. Without it a peer can move pkt.dpo past the end
of the reassembled buffer, and the local EOMA loopback then hands a
NULL skb to j1939_sk_recv().
[3/4] guards j1939_session_completed() against that NULL anyway. With
[2/4] in place this should be unreachable; it turns a future
regression in the offset arithmetic into a dropped datagram instead
of a kernel panic.
[4/4] rejects an oversized ETP.CM_CTS at the point where it is parsed,
instead of letting it travel back as a bad EDPO offset.
Testing:
- [2/4] alone was verified against a local vcan setup: an ETP session with
ETP.CM_RTS size 1786 (256 packets) followed by ETP.CM_DPO packet 256 and
an ETP.DT with sequence 0 gave a NULL dereference in j1939_sk_recv()
3/3 times before the change and was rejected 3/3 times after it.
- The T5/T6 hang from the review ([1/4]): two unparsable ETP.DT frames
followed by "ip link del" left unregister_netdevice() waiting for the
session reference; with the rearmed timer the session is deactivated
500 ms after the second frame.
- Each patch builds standalone; net/can/j1939/ is unchanged in behaviour
for anything that does not involve an EDPO, a CTS or an abort.
Changes in v7:
- Split into four patches, as promised in the reply to the v6 review.
- [1/4] replaces the "waiting" branch with rearming the abort timer under
the lock. Re-checked the lock order: none of the callers of
j1939_tp_set_rxtimeout() hold active_session_list_lock, so taking it for
the rearm does not introduce a new ordering.
- [2/4] uses J1939_XTP_ABORT_BAD_EDPO_OFFSET for the receiver, which is
what the comment next to the definition describes and which had no
callers; J1939_XTP_ABORT_ECTS_TOO_BIG is left to [4/4].
- Revision history moved below the --- line, Author and Signed-off-by now
match, and Closes: replaces Link: after Reported-by:.
Bounding the CTS packet number is [4/4] rather than part of [2/4], because
it addresses a different input and can be reviewed on its own.
Chaosheng Qu (4):
can: j1939: arm the abort timer under the session list lock
can: j1939: validate the EDPO packet number
can: j1939: drop the message when the reassembled skb is gone
can: j1939: reject a CTS that names more packets than the message has
net/can/j1939/transport.c | 84 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 79 insertions(+), 5 deletions(-)
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock 2026-11-08 1:34 [PATCH net v7 0/4] can: j1939: fix the abort timer and bound EDPO Chaosheng Qu @ 2026-11-08 1:34 ` Chaosheng Qu 2026-11-11 6:45 ` sashiko-bot 2026-11-12 21:49 ` netdev-bot+sashiko 2026-11-08 1:34 ` [PATCH net v7 4/4] can: j1939: reject a CTS that names more packets than the message has Chaosheng Qu ` (2 subsequent siblings) 3 siblings, 2 replies; 11+ messages in thread From: Chaosheng Qu @ 2026-11-08 1:34 UTC (permalink / raw) To: Robin van der Gracht, Oleksij Rempel, Oliver Hartkopp, Marc Kleine-Budde Cc: kernel, linux-can, linux-kernel From 672c37cb66569c9103f28405d455c8777c81c9e0 Mon Sep 17 00:00:00 2001 From: Chaosheng Qu <quchaosheng000406@163.com> Date: Sun, 8 Nov 2026 09:22:28 +0800 Subject: [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock j1939_session_cancel() cancels the receive timer while holding active_session_list_lock. hrtimer_cancel() waits for a callback that is already running, and j1939_tp_rxtimer() takes the same lock, so a softirq callback waiting for the lock deadlocks against the CPU that holds it. Cancel the timer before taking the lock instead. Cancelling outside the lock opens a second window: the cancel/get/start sequence is no longer atomic against j1939_tp_set_rxtimeout(), which rearms the same timer from the receive path without holding the lock. A concurrent arm in that window queues a timer whose reference is never dropped, and the session then never reaches j1939_session_destroy(), keeping both its netdev and its j1939_priv reference alive. Take the lock for the rearms so both armers are serialized; hrtimer_start() does not wait for a callback, so it is safe to call while holding it. A session that is already in J1939_SESSION_WAITING_ABORT also gets its timer back. A second unparsable frame re-enters j1939_session_cancel() after j1939_session_timers_cancel() has killed the timer, and without a rearmed timer nothing ever moves that session out of WAITING_ABORT, so unregistering the device hangs in unregister_netdevice(). The reference accounting stays balanced: one j1939_session_get() per hrtimer_start(), dropped by j1939_session_put() in j1939_tp_rxtimer() when the timer fires. Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com> --- net/can/j1939/transport.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index 8fcfd13e5e6f..590a04c453b5 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -1161,12 +1161,38 @@ static void __j1939_session_cancel(struct j1939_session *session, static void j1939_session_cancel(struct j1939_session *session, enum j1939_xtp_abort err) { + bool abort_sent = false; + + /* Cancel the abort timer before taking the list lock. + * + * hrtimer_cancel() waits for a callback that is already running, and + * j1939_tp_rxtimer() takes the same lock, so cancelling the timer while + * holding active_session_list_lock can deadlock: the lock holder waits + * for the callback, the callback waits for the lock. + */ + j1939_session_rxtimer_cancel(session); + j1939_session_list_lock(session->priv); 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); + abort_sent = true; + } + + /* Re-arm under the lock, whether the abort frame just went out or the + * session is already in WAITING_ABORT and only needs its timer back. + * + * hrtimer_start() does not wait for a running callback, so arming here + * cannot deadlock, and taking the lock closes the window against + * j1939_tp_set_rxtimeout(), which rearms the same timer from the + * receive path without holding it. + */ + if (abort_sent || session->state == J1939_SESSION_WAITING_ABORT) { + j1939_session_get(session); + hrtimer_start(&session->rxtimer, + ms_to_ktime(J1939_XTP_ABORT_TIMEOUT_MS), + HRTIMER_MODE_REL_SOFT); } j1939_session_list_unlock(session->priv); -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock 2026-11-08 1:34 ` [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock Chaosheng Qu @ 2026-11-11 6:45 ` sashiko-bot 2026-11-12 21:49 ` netdev-bot+sashiko 1 sibling, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-11-11 6:45 UTC (permalink / raw) To: Chaosheng Qu Cc: linux-can, Marc Kleine-Budde, Oliver Hartkopp, Vincent Mailhol, Oleksij Rempel Thank you for your contribution! Sashiko AI review found 3 potential issues to consider: - [High] can: j1939: race in j1939_session_cancel() double-arms rxtimer and leaks session - [Medium] can: j1939: j1939_session_cancel() leaves rxtimer armed on deactivated session - [Low] can: j1939: missing Fixes trailer for commit d966635b384b -- commit d2f1f6071a930db192083744d44435260ec61c4f Author: Chaosheng Qu <quchaosheng000406@163.com> > can: j1939: arm the abort timer under the session list lock > > j1939_session_cancel() cancels the receive timer while holding > active_session_list_lock. hrtimer_cancel() waits for a callback that is > already running, and j1939_tp_rxtimer() takes the same lock, so a softirq > callback waiting for the lock deadlocks against the CPU that holds it. > Cancel the timer before taking the lock instead. > > Cancelling outside the lock opens a second window: the cancel/get/start > sequence is no longer atomic against j1939_tp_set_rxtimeout(), which rearms > the same timer from the receive path without holding the lock. A > concurrent > arm in that window queues a timer whose reference is never dropped, and the > session then never reaches j1939_session_destroy(), keeping both its netdev > and its j1939_priv reference alive. Take the lock for the rearms so both > armers are serialized; hrtimer_start() does not wait for a callback, so it > is safe to call while holding it. > > A session that is already in J1939_SESSION_WAITING_ABORT also gets its > timer > back. A second unparsable frame re-enters j1939_session_cancel() after > j1939_session_timers_cancel() has killed the timer, and without a rearmed > timer nothing ever moves that session out of WAITING_ABORT, so > unregistering > the device hangs in unregister_netdevice(). > > The reference accounting stays balanced: one j1939_session_get() per > hrtimer_start(), dropped by j1939_session_put() in j1939_tp_rxtimer() when > the timer fires. > > Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com> [Severity: Low] This fixes the locking and timer re-arming in j1939_session_cancel(). Should this include: Fixes: d966635b384b ("can: j1939: transport: make sure the aborted session will be deactivated only once") > diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c > index 8fcfd13e5e6f8..590a04c453b56 100644 > --- a/net/can/j1939/transport.c > +++ b/net/can/j1939/transport.c > @@ -1161,12 +1161,38 @@ static void __j1939_session_cancel(struct j1939_session *session, > static void j1939_session_cancel(struct j1939_session *session, > enum j1939_xtp_abort err) > { > + bool abort_sent = false; > + > + /* Cancel the abort timer before taking the list lock. > + * > + * hrtimer_cancel() waits for a callback that is already running, and > + * j1939_tp_rxtimer() takes the same lock, so cancelling the timer while > + * holding active_session_list_lock can deadlock: the lock holder waits > + * for the callback, the callback waits for the lock. > + */ > + j1939_session_rxtimer_cancel(session); > + > j1939_session_list_lock(session->priv); > > 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); > + abort_sent = true; > } > + > + /* Re-arm under the lock, whether the abort frame just went out or the > + * session is already in WAITING_ABORT and only needs its timer back. > + * > + * hrtimer_start() does not wait for a running callback, so arming here > + * cannot deadlock, and taking the lock closes the window against > + * j1939_tp_set_rxtimeout(), which rearms the same timer from the > + * receive path without holding it. > + */ [Severity: High] Can a concurrent timer arm between j1939_session_rxtimer_cancel() and hrtimer_start() leak a session reference? j1939_tp_set_rxtimeout() does not acquire active_session_list_lock, so taking the lock here does not serialize the two paths. Even between two concurrent j1939_session_cancel() callers (for example from j1939_tp_txtimer() and j1939_tp_recv()), both cancel rxtimer before taking the lock: CPU0 CPU1 j1939_session_cancel() j1939_session_rxtimer_cancel() j1939_session_cancel() j1939_session_rxtimer_cancel() j1939_session_list_lock() session->state = ..._WAITING_ABORT j1939_session_get(session) hrtimer_start(&session->rxtimer) j1939_session_list_unlock() j1939_session_list_lock() /* state == WAITING_ABORT */ j1939_session_get(session) hrtimer_start(&session->rxtimer) <-- re-queues armed timer, leaks ref Calling hrtimer_start() on an already-queued rxtimer replaces it in place without dropping the previous reference, so session->kref never reaches zero and unregister_netdevice() hangs waiting for the leaked netdev reference. > + if (abort_sent || session->state == J1939_SESSION_WAITING_ABORT) { > + j1939_session_get(session); > + hrtimer_start(&session->rxtimer, > + ms_to_ktime(J1939_XTP_ABORT_TIMEOUT_MS), > + HRTIMER_MODE_REL_SOFT); > + } [Severity: Medium] Does re-arming session->rxtimer when session->state is already J1939_SESSION_WAITING_ABORT leave rxtimer running on a deactivated session in j1939_xtp_rx_rts_session_active()? When a duplicate RTS frame arrives on an active session with session->last_cmd != 0, j1939_xtp_rx_rts_session_active() cancels and immediately deactivates the session: net/can/j1939/transport.c:j1939_xtp_rx_rts_session_active() { ... 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) { ... j1939_session_deactivate_activate_next(session); } } With this change, j1939_session_cancel() re-arms rxtimer, and j1939_session_deactivate_activate_next() then sets session->state to J1939_SESSION_DONE and removes the session from priv->active_session_list without cancelling rxtimer. When rxtimer fires 500ms later on the J1939_SESSION_DONE session, j1939_tp_rxtimer() takes the else branch and sends a duplicate J1939_ERRQUEUE_RX_ABORT notification. -- Sashiko AI review · https://sashiko.dev/#/patchset/179410166344.60581.3898011861151549156@163.com?part=1 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock 2026-11-08 1:34 ` [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock Chaosheng Qu 2026-11-11 6:45 ` sashiko-bot @ 2026-11-12 21:49 ` netdev-bot+sashiko 1 sibling, 0 replies; 11+ messages in thread From: netdev-bot+sashiko @ 2026-11-12 21:49 UTC (permalink / raw) To: quchaosheng000406 Cc: robin, o.rempel, socketcan, mkl, kernel, linux-can, linux-kernel, kuba Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider. Critical: 0 · High: 2 · Medium: 1 · Low: 4 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [High] The commit message says the rearms take the list lock "so both armers are serialized". - [High] This is a new regression: two j1939_session_cancel() calls running at the same time on the same session now double-arm the… - [Medium] j1939_session_cancel() now also re-arms the rxtimer when the state is already WAITING_ABORT. - [Low] The patch fixes a deadlock (hrtimer_cancel() under active_session_list_lock against j1939_tp_rxtimer(), which takes that… - [Low] The subject, 'can: j1939: arm the abort timer under the session list lock', does not describe the change. - [Low] This behaviour change is not described in the commit message. - [Low] The flag abort_sent and the comment 'whether the abort frame just went out or the session is already in WAITING_ABORT' in… -- can: j1939: arm the abort timer under the session list lock This patch moves the rxtimer cancel in j1939_session_cancel() outside of active_session_list_lock, to avoid a deadlock against j1939_tp_rxtimer(). It then re-arms the abort timer under the lock. It also re-arms the timer for sessions that are already in J1939_SESSION_WAITING_ABORT. > can: j1939: arm the abort timer under the session list lock [Severity: Low] This isn't a bug, but does the subject describe the change? Before this patch, j1939_session_cancel() already armed the abort timer under the lock, because j1939_tp_set_rxtimeout() was called inside j1939_session_list_lock(). The patch makes two changes. It moves the cancel out of the lock, which is the deadlock fix. It also re-arms WAITING_ABORT sessions, which is the stuck-session fix. Would these be easier to bisect and backport as two separate patches? [ ... ] > Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com> [Severity: Low] Should this have a Fixes: tag and Cc: stable? git blame shows that j1939_session_cancel(), with j1939_tp_set_rxtimeout() called inside the lock, comes from commit d966635b384b. The related fix 1809c82aa073 for the same session leak class carried "Fixes: 9d71dd0c7009" and "Cc: stable@vger.kernel.org". > diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c > index 8fcfd13e5e6f8..590a04c453b56 100644 > --- a/net/can/j1939/transport.c > +++ b/net/can/j1939/transport.c > @@ -1161,12 +1161,38 @@ static void __j1939_session_cancel(struct j1939_session *session, > static void j1939_session_cancel(struct j1939_session *session, > enum j1939_xtp_abort err) > { > + bool abort_sent = false; > + > + /* Cancel the abort timer before taking the list lock. > + * > + * hrtimer_cancel() waits for a callback that is already running, and > + * j1939_tp_rxtimer() takes the same lock, so cancelling the timer while > + * holding active_session_list_lock can deadlock: the lock holder waits > + * for the callback, the callback waits for the lock. > + */ > + j1939_session_rxtimer_cancel(session); > + > j1939_session_list_lock(session->priv); > > 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); > + abort_sent = true; > + } [Severity: Low] This isn't a bug, but the name abort_sent doesn't quite match the code. __j1939_session_cancel() skips j1939_xtp_tx_abort() for broadcast sessions: net/can/j1939/transport.c:__j1939_session_cancel() { ... session->state = J1939_SESSION_WAITING_ABORT; /* do not send aborts on incoming broadcasts */ if (!j1939_cb_is_broadcast(&session->skcb)) { ... } abort_sent is still set to true in that case. __j1939_session_cancel() also always sets J1939_SESSION_WAITING_ABORT, so the state check below is already true whenever abort_sent is. Is the flag needed at all? The comment below ("whether the abort frame just went out or the session is already in WAITING_ABORT") describes two cases that the code never distinguishes. > + > + /* Re-arm under the lock, whether the abort frame just went out or the > + * session is already in WAITING_ABORT and only needs its timer back. > + * > + * hrtimer_start() does not wait for a running callback, so arming here > + * cannot deadlock, and taking the lock closes the window against > + * j1939_tp_set_rxtimeout(), which rearms the same timer from the > + * receive path without holding it. > + */ [Severity: High] Does taking the lock here actually close the window against j1939_tp_set_rxtimeout()? That function is unchanged, and it still does cancel, get and start without active_session_list_lock: net/can/j1939/transport.c:j1939_tp_set_rxtimeout() { j1939_session_rxtimer_cancel(session); j1939_session_get(session); hrtimer_start(&session->rxtimer, ms_to_ktime(msec), HRTIMER_MODE_REL_SOFT); } None of its callers hold the list lock either. That covers the RX softirq paths and the txtimer paths: j1939_session_tx_rts(), j1939_session_tx_dpo(), j1939_session_tx_dat(), j1939_session_tx_cts(), j1939_session_tx_eoma() and j1939_simple_txnext(). Only one of the two armers takes the lock. Are they really serialized, as the commit message says ("so both armers are serialized")? The cancel is now outside the lock. Can this sequence leak a session reference? CPU A (j1939_session_cancel) CPU B (j1939_tp_set_rxtimeout) j1939_session_rxtimer_cancel() j1939_session_rxtimer_cancel() (nothing pending) j1939_session_get() hrtimer_start() j1939_session_list_lock() __j1939_session_cancel() j1939_session_get() hrtimer_start() (re-enqueues the already queued timer) Both sides take a reference, but only one timer is queued. j1939_tp_rxtimer() calls j1939_session_put() once per expiry. The reverse order leaks the same way. Once a kref leaks, j1939_session_destroy() never runs. So netdev_put(), j1939_priv_put() and, for TX sessions, the sock_put() in __j1939_session_drop() never happen either. Wouldn't unregister_netdevice() then hang, which is the leak the commit message says this patch closes? j1939_cancel_active_session() at unregister doesn't look able to recover this. It only drops one rxtimer reference through hrtimer_try_to_cancel(), plus the list reference. Would it help to use hrtimer_try_to_cancel() under the lock and put when it returns 1, as j1939_cancel_active_session() does? Another option is to tie the get to whether the timer was actually inactive. > + if (abort_sent || session->state == J1939_SESSION_WAITING_ABORT) { > + j1939_session_get(session); > + hrtimer_start(&session->rxtimer, > + ms_to_ktime(J1939_XTP_ABORT_TIMEOUT_MS), > + HRTIMER_MODE_REL_SOFT); > } [Severity: High] Can two concurrent j1939_session_cancel() calls on the same session double-arm the rxtimer here? For example, CPU A is in j1939_tp_txtimer()->j1939_session_cancel(). CPU B is in an RX out_session_cancel path such as j1939_xtp_rx_dat_one() or j1939_xtp_rx_cts_one(): CPU A CPU B j1939_session_rxtimer_cancel() hrtimer_cancel() returns 1, put j1939_session_rxtimer_cancel() (nothing pending) j1939_session_list_lock() __j1939_session_cancel() state = WAITING_ABORT j1939_session_get() hrtimer_start() j1939_session_list_unlock() j1939_session_list_lock() state == WAITING_ABORT j1939_session_get() hrtimer_start() (re-enqueues the queued timer) j1939_session_list_unlock() That is two gets for a single expiry of j1939_tp_rxtimer(), so one kref would never be dropped. Before this patch, CPU B saw WAITING_ABORT under the lock and did nothing. The WAITING_ABORT re-arm combined with the lockless cancel seems to be what introduces this. The commit message says "The reference accounting stays balanced: one j1939_session_get() per hrtimer_start()". Doesn't that only hold if hrtimer_start() is never called on a timer that is already queued? [Severity: Medium] Does the WAITING_ABORT re-arm conflict with the existing workaround in j1939_xtp_rx_rts_session_active()? That code still assumes j1939_session_cancel() won't restart the timer in WAITING_ABORT: net/can/j1939/transport.c:j1939_xtp_rx_rts_session_active() { ... 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); } ... } Take a receive session where the peer sends a second RTS. The first j1939_session_timers_cancel() and j1939_session_cancel() pair at the top of this function moves the session to WAITING_ABORT and arms the timer. The second pair then kills that timer. With this patch, j1939_session_cancel() re-arms it with a new reference. j1939_session_deactivate_activate_next() then moves the session to DONE without cancelling the timer. When that timer fires on the DONE session, j1939_tp_rxtimer() takes the generic else branch. The state check fails, but it still runs: if (!session->sk) j1939_sk_errqueue(session, J1939_ERRQUEUE_RX_ABORT); Doesn't this deliver an extra J1939_ERRQUEUE_RX_ABORT to matching sockets for a session that has already finished? It also keeps the session, netdev and priv references alive for another J1939_XTP_ABORT_TIMEOUT_MS. The comment in j1939_xtp_rx_rts_session_active() would also no longer match what j1939_session_cancel() does. [Severity: Low] Is this change in timeout semantics intended? Every j1939_session_cancel() on a session already in WAITING_ABORT now cancels the pending abort timer and starts a fresh J1939_XTP_ABORT_TIMEOUT_MS timer. That includes the calls from j1939_tp_txtimer(), which don't cancel the timers first: if (session->skcb.addr.type != J1939_SIMPLE) { j1939_session_cancel(session, J1939_XTP_ABORT_OTHER); Before this patch, those calls did nothing and the original abort deadline stood. Now the forced deactivation happens 500ms after the last cancel, not 500ms after the abort. A peer could keep sending malformed TP frames less than 500ms apart, for example DT frames with skb->len != 8 that reach out_session_cancel in j1939_xtp_rx_dat_one(). Wouldn't that keep the session in WAITING_ABORT on the active list indefinitely? The commit message only says such sessions "also gets its timer back". Could it mention that an existing pending deadline is also pushed back? > > j1939_session_list_unlock(session->priv); [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/179410166344.60581.3898011861151549156%40163.com ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v7 4/4] can: j1939: reject a CTS that names more packets than the message has 2026-11-08 1:34 [PATCH net v7 0/4] can: j1939: fix the abort timer and bound EDPO Chaosheng Qu 2026-11-08 1:34 ` [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock Chaosheng Qu @ 2026-11-08 1:34 ` Chaosheng Qu 2026-11-12 21:49 ` netdev-bot+sashiko 2026-11-08 1:34 ` [PATCH net v7 3/4] can: j1939: drop the message when the reassembled skb is gone Chaosheng Qu 2026-11-08 1:34 ` [PATCH net v7 2/4] can: j1939: validate the EDPO packet number Chaosheng Qu 3 siblings, 1 reply; 11+ messages in thread From: Chaosheng Qu @ 2026-11-08 1:34 UTC (permalink / raw) To: Robin van der Gracht, Oleksij Rempel, Oliver Hartkopp, Marc Kleine-Budde Cc: kernel, linux-can, linux-kernel From d4e65724a76b0d8570b7e4603948219f11128999 Mon Sep 17 00:00:00 2001 From: Chaosheng Qu <quchaosheng000406@163.com> Date: Sun, 8 Nov 2026 09:22:30 +0800 Subject: [PATCH net v7 4/4] can: j1939: reject a CTS that names more packets than the message has j1939_xtp_rx_cts_one() rejects a zero packet number and a window above pkt.block, but not a packet number above pkt.total. It then stores "pkt - 1" in pkt.tx_acked, and the transmitter reports that value back in an ETP.CM_DPO. That value is what j1939_xtp_rx_dpo_one() now bounds, so an oversized CTS is currently caught one step later and reported as a bad EDPO offset. Reject it where it originates instead, with the abort code that describes it: J1939_XTP_ABORT_ECTS_TOO_BIG, "ECTS requested packets exceeds message size". Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com> --- net/can/j1939/transport.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index 5f05c15114ce..6cb3dec49f3f 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -1504,6 +1504,15 @@ j1939_xtp_rx_cts_one(struct j1939_session *session, struct sk_buff *skb) goto out_session_cancel; else if (dat[1] > session->pkt.block /* 0xff for etp */) goto out_session_cancel; + else if (pkt > session->pkt.total) { + /* The responder asks for more packets than the message has. + * pkt - 1 becomes pkt.tx_acked below, and the transmitter + * reports that value in an ETP.CM_DPO, where it would name a + * packet past the end of the message. + */ + err = J1939_XTP_ABORT_ECTS_TOO_BIG; + goto out_session_cancel; + } /* set packet counters only when not CTS(0) */ session->pkt.tx_acked = pkt - 1; -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH net v7 4/4] can: j1939: reject a CTS that names more packets than the message has 2026-11-08 1:34 ` [PATCH net v7 4/4] can: j1939: reject a CTS that names more packets than the message has Chaosheng Qu @ 2026-11-12 21:49 ` netdev-bot+sashiko 0 siblings, 0 replies; 11+ messages in thread From: netdev-bot+sashiko @ 2026-11-12 21:49 UTC (permalink / raw) To: quchaosheng000406 Cc: robin, o.rempel, socketcan, mkl, kernel, linux-can, linux-kernel, kuba Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 1 · Low: 1 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [Medium] The commit message of "can: j1939: reject a CTS that names more packets than the message has" gets the current behaviour wrong… - [Low] This patch makes the transmitter-role branch of j1939_xtp_rx_dpo_one() (net/can/j1939/transport.c, lines 1894-1899)… -- can: j1939: reject a CTS that names more packets than the message has This patch adds a check to j1939_xtp_rx_cts_one(). A CTS whose packet number is above pkt.total is now rejected, and the session is aborted with J1939_XTP_ABORT_ECTS_TOO_BIG. Before this, the function only rejected a zero packet number and a window above pkt.block. > diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c > index 5f05c15114ce4..6cb3dec49f3f7 100644 > --- a/net/can/j1939/transport.c > +++ b/net/can/j1939/transport.c > @@ -1504,6 +1504,15 @@ j1939_xtp_rx_cts_one(struct j1939_session *session, struct sk_buff *skb) > goto out_session_cancel; > else if (dat[1] > session->pkt.block /* 0xff for etp */) > goto out_session_cancel; > + else if (pkt > session->pkt.total) { > + /* The responder asks for more packets than the message has. > + * pkt - 1 becomes pkt.tx_acked below, and the transmitter > + * reports that value in an ETP.CM_DPO, where it would name a > + * packet past the end of the message. > + */ > + err = J1939_XTP_ABORT_ECTS_TOO_BIG; > + goto out_session_cancel; > + } [Severity: Medium] Does the commit message describe the current behaviour correctly? It says: That value is what j1939_xtp_rx_dpo_one() now bounds, so an oversized CTS is currently caught one step later and reported as a bad EDPO offset. Before this patch, the host that receives an oversized ETP CTS goes through this path: j1939_xtp_rx_cts_one() session->pkt.tx_acked = pkt - 1; /* >= pkt.total */ j1939_xtp_txnext_transmiter() j1939_session_tx_dpo() dat[1] = session->pkt.last - session->pkt.tx_acked; /* 0 or wraps */ local echo of that DPO -> j1939_tp_cmd_recv() j1939_xtp_rx_dpo(priv, skb, true) j1939_xtp_rx_dpo_one() In j1939_xtp_rx_dpo_one(), the echo then takes the transmitter branch: if (dpo >= session->pkt.total) { if (session->transmission) { ... j1939_session_cancel(session, J1939_XTP_ABORT_ECTS_TOO_BIG); } else { j1939_session_cancel(session, J1939_XTP_ABORT_BAD_EDPO_OFFSET); } So isn't the abort code already ECTS_TOO_BIG, not a bad EDPO offset? The earlier commit in this series, "can: j1939: validate the EDPO packet number", says the same. For ETP, the actual change seems to be that the out-of-range DPO, with a zero or wrapped dat[1], is no longer sent on the bus. The new check also runs for plain TP sessions. There, pkt = dat[2] and there is no DPO step. Before this patch, an oversized TP CTS reached j1939_session_tx_dat() with pkt.tx >= pkt.total: se_skb = j1939_session_skb_get_by_offset(session, session->pkt.tx * 7); if (!se_skb) return -ENOBUFS; The txtimer then retried until one of two things happened: - the rxtimer aborted the session with J1939_XTP_ABORT_TIMEOUT, or - the retry limit ended the session with -ENETUNREACH. With this patch, a TP session aborts at once with code 15. That code is documented as "ECTS requested packets exceeds message size". Could the commit message and the new comment cover the TP case as well? The peer and the socket user will see this change. The comment currently explains the check only through ETP.CM_DPO. > > /* set packet counters only when not CTS(0) */ > session->pkt.tx_acked = pkt - 1; [Severity: Low] With this check in place, does the transmitter branch in j1939_xtp_rx_dpo_one() still match its comment? net/can/j1939/transport.c:j1939_xtp_rx_dpo_one() { ... if (session->transmission) { /* A transmitter only builds an EDPO from pkt.tx_acked, * which comes from the responder's ETP.CM_CTS, so an * out-of-range value there is an oversized CTS. */ j1939_session_cancel(session, J1939_XTP_ABORT_ECTS_TOO_BIG); ... } After this patch, pkt.tx_acked is at most pkt.total - 1 here. The one exception is j1939_xtp_rx_eoma_one(), which sets it to pkt.total just before the session completes. j1939_session_tx_dpo() copies pkt.tx_acked into the DPO packet number, so a DPO built by the local transmitter can no longer reach dpo >= pkt.total. The only frame that can still reach this branch is an ETP.CM_DPO with a local SA that the local transmitter did not build. One example is another node on the bus using the same SA. Another is a raw CAN frame injected on the interface. j1939_tp_cmd_recv() passes such a frame to j1939_xtp_rx_dpo(priv, skb, true). For that frame, wouldn't J1939_XTP_ABORT_BAD_EDPO_OFFSET be the more accurate abort reason? The commit message refers to the bound in j1939_xtp_rx_dpo_one(). Should that role split and its comment be updated as part of this patch? -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/179410166344.60581.3898011861151549156%40163.com ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v7 3/4] can: j1939: drop the message when the reassembled skb is gone 2026-11-08 1:34 [PATCH net v7 0/4] can: j1939: fix the abort timer and bound EDPO Chaosheng Qu 2026-11-08 1:34 ` [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock Chaosheng Qu 2026-11-08 1:34 ` [PATCH net v7 4/4] can: j1939: reject a CTS that names more packets than the message has Chaosheng Qu @ 2026-11-08 1:34 ` Chaosheng Qu 2026-11-12 21:49 ` netdev-bot+sashiko 2026-11-08 1:34 ` [PATCH net v7 2/4] can: j1939: validate the EDPO packet number Chaosheng Qu 3 siblings, 1 reply; 11+ messages in thread From: Chaosheng Qu @ 2026-11-08 1:34 UTC (permalink / raw) To: Robin van der Gracht, Oleksij Rempel, Oliver Hartkopp, Marc Kleine-Budde Cc: kernel, linux-can, linux-kernel From ba4643113b5f6ad2b53b98dae1c25d5465d4d0c0 Mon Sep 17 00:00:00 2001 From: Chaosheng Qu <quchaosheng000406@163.com> Date: Sun, 8 Nov 2026 09:22:29 +0800 Subject: [PATCH net v7 3/4] can: j1939: drop the message when the reassembled skb is gone j1939_session_completed() takes the skb for the completed message and hands it to j1939_sk_recv() without checking the result. j1939_session_skb_get() returns NULL when the offset it computes lands past the end of the queue. The offset is "pkt.dpo * 7", and pkt.dpo is bounded when an EDPO is received, so this should not happen. Guard it anyway: a NULL dereference in the receive path is a kernel panic, while a dropped message is a lost datagram. j1939_simple_txnext(), the only other caller of j1939_session_skb_get(), already checks for NULL. Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com> --- net/can/j1939/transport.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index b0e62822b56e..5f05c15114ce 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -1269,9 +1269,19 @@ static void j1939_session_completed(struct j1939_session *session) if (!session->transmission) { se_skb = j1939_session_skb_get(session); - /* distribute among j1939 receivers */ - j1939_sk_recv(session->priv, se_skb); - consume_skb(se_skb); + + /* Defense in depth: pkt.dpo is range checked when an EDPO is + * received, so the lookup should always find the skb. Drop the + * message rather than hand a NULL skb to j1939_sk_recv() if the + * offset arithmetic ever regresses. The only other caller of + * j1939_session_skb_get(), j1939_simple_txnext(), already does + * this. + */ + if (se_skb) { + /* distribute among j1939 receivers */ + j1939_sk_recv(session->priv, se_skb); + consume_skb(se_skb); + } } j1939_session_deactivate_activate_next(session); -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH net v7 3/4] can: j1939: drop the message when the reassembled skb is gone 2026-11-08 1:34 ` [PATCH net v7 3/4] can: j1939: drop the message when the reassembled skb is gone Chaosheng Qu @ 2026-11-12 21:49 ` netdev-bot+sashiko 0 siblings, 0 replies; 11+ messages in thread From: netdev-bot+sashiko @ 2026-11-12 21:49 UTC (permalink / raw) To: quchaosheng000406 Cc: robin, o.rempel, socketcan, mkl, kernel, linux-can, linux-kernel, kuba Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin. Low priority / nit picks for your consideration: - [Low] The new NULL branch in j1939_session_completed() (net/can/j1939/transport.c) drops the reassembled message without… - [Low] This patch makes the comment in j1939_xtp_rx_dpo_one() (net/can/j1939/transport.c, lines 1871-1883) out of date. -- can: j1939: drop the message when the reassembled skb is gone This adds a NULL check in j1939_session_completed() on the skb returned by j1939_session_skb_get(). A failed lookup now drops the reassembled message instead of passing NULL to j1939_sk_recv(). The commit message says pkt.dpo is already bounded when an EDPO is received, so this is defense in depth. > diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c > index b0e62822b56e4..5f05c15114ce4 100644 > --- a/net/can/j1939/transport.c > +++ b/net/can/j1939/transport.c > @@ -1269,9 +1269,19 @@ static void j1939_session_completed(struct j1939_session *session) > > if (!session->transmission) { > se_skb = j1939_session_skb_get(session); > - /* distribute among j1939 receivers */ > - j1939_sk_recv(session->priv, se_skb); > - consume_skb(se_skb); > + > + /* Defense in depth: pkt.dpo is range checked when an EDPO is > + * received, so the lookup should always find the skb. Drop the > + * message rather than hand a NULL skb to j1939_sk_recv() if the > + * offset arithmetic ever regresses. The only other caller of > + * j1939_session_skb_get(), j1939_simple_txnext(), already does > + * this. > + */ > + if (se_skb) { > + /* distribute among j1939 receivers */ > + j1939_sk_recv(session->priv, se_skb); > + consume_skb(se_skb); > + } [Severity: Low] If the offset arithmetic does regress, would anyone notice? When se_skb is NULL, the message is dropped with no WARN. session->err stays at zero, and nothing is queued for userspace. The only output is the netdev_dbg() in j1939_session_skb_get_by_offset(), which is off unless dynamic debug is enabled. On the userspace side, j1939_session_destroy() only reports RX_ABORT when session->err is set: } else if (session->err) { j1939_sk_errqueue(session, J1939_ERRQUEUE_RX_ABORT); } A receiver using SOF_TIMESTAMPING_RX_SOFTWARE would see J1939_ERRQUEUE_RX_RTS, and possibly RX_DPO. After that it would get neither the data nor an RX_ABORT. The commit message also says: a dropped message is a lost datagram. For an RTS/CTS receive session, j1939_session_completed() is reached from j1939_xtp_rx_eoma_one() when the receiver's own EOMA comes back on loopback. By then the originator has already been told the message was delivered, so the two sides would disagree about delivery. This branch cannot be reached in the current tree. j1939_xtp_rx_dpo_one() rejects dpo >= session->pkt.total before storing it, and TP and BAM sessions keep pkt.dpo at 0. The concern is that the guard would not catch the regression it was added for. Would WARN_ON_ONCE(!se_skb) plus setting session->err (for example to -EBADMSG) fit better here? That would show the condition in the logs and deliver the RX_ABORT errqueue notification. [Severity: Low] This isn't a bug, but this change makes a comment in j1939_xtp_rx_dpo_one() out of date. That comment was added by "can: j1939: validate the EDPO packet number" and still says: * j1939_session_completed(), which asks j1939_session_skb_get() for a * packet past the end of the queued skb, gets NULL, and passes it to * j1939_sk_recv(). With the new if (se_skb) check, a failed lookup no longer reaches j1939_sk_recv(). Without the EDPO bound, the message would now be silently dropped after the receiver's EOMA has been sent. Could that comment be updated along with this change? The last patch in the series, "can: j1939: reject a CTS that names more packets than the message has", only touches j1939_xtp_rx_cts_one(). The comment is still stale at the end of the series. [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/179410166344.60581.3898011861151549156%40163.com ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v7 2/4] can: j1939: validate the EDPO packet number 2026-11-08 1:34 [PATCH net v7 0/4] can: j1939: fix the abort timer and bound EDPO Chaosheng Qu ` (2 preceding siblings ...) 2026-11-08 1:34 ` [PATCH net v7 3/4] can: j1939: drop the message when the reassembled skb is gone Chaosheng Qu @ 2026-11-08 1:34 ` Chaosheng Qu 2026-11-11 7:04 ` sashiko-bot 2026-11-12 21:49 ` netdev-bot+sashiko 3 siblings, 2 replies; 11+ messages in thread From: Chaosheng Qu @ 2026-11-08 1:34 UTC (permalink / raw) To: Robin van der Gracht, Oleksij Rempel, Oliver Hartkopp, Marc Kleine-Budde Cc: kernel, linux-can, linux-kernel From 4fca2aa63eeaefe7f14a6b1c5b9a3b67a2439c98 Mon Sep 17 00:00:00 2001 From: Chaosheng Qu <quchaosheng000406@163.com> Date: Sun, 8 Nov 2026 09:22:29 +0800 Subject: [PATCH net v7 2/4] can: j1939: validate the EDPO packet number ETP.CM_DPO is peer controlled and its packet number is accepted without any validation. It names the packet a transmission window starts at, and j1939_xtp_rx_dat_one() places incoming data at "dat[0] - 1 + pkt.dpo", so a value past the end of the message moves pkt.dpo beyond the reassembled buffer. The transfer still completes normally in that case. With pkt.dpo == pkt.total, a closing ETP.DT frame with dat[0] == 0 evaluates to packet pkt.total - 1, which is the last in-order packet, so pkt.rx reaches pkt.total and the receiver emits its own EOMA. The local loopback of that EOMA runs j1939_session_completed(), which asks j1939_session_skb_get() for offset "pkt.dpo * 7" -- one full packet past the end of the queued skb -- gets NULL, and passes it straight to j1939_sk_recv(), which dereferences oskb->sk. Reject an EDPO that names a packet at or past pkt.total and abort the session. The check has to be ">=" and not ">": pkt.total is a packet count, so the last valid packet is pkt.total - 1. The abort code depends on the role. A receiver rejects a bad offset with J1939_XTP_ABORT_BAD_EDPO_OFFSET, which the comment next to the definition describes as "Bad EDPO offset" and which had no callers. A transmitter only builds an EDPO from pkt.tx_acked, which comes from the responder's ETP.CM_CTS, so an out-of-range value there is an oversized CTS and is reported as J1939_XTP_ABORT_ECTS_TOO_BIG. Bounding the CTS packet number in j1939_xtp_rx_cts_one() is a separate change; this one only stops the value from being used as an offset. Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com> --- net/can/j1939/transport.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index 590a04c453b5..b0e62822b56e 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -1849,14 +1849,43 @@ static void j1939_xtp_rx_dpo_one(struct j1939_session *session, struct sk_buff *skb) { const u8 *dat = skb->data; + unsigned int dpo; if (j1939_xtp_rx_cmd_bad_pgn(session, skb)) return; netdev_dbg(session->priv->ndev, "%s: 0x%p\n", __func__, session); + dpo = j1939_etp_ctl_to_packet(dat); + + /* ETP.CM_DPO is peer controlled. j1939_xtp_rx_dat_one() places data at + * "dat[0] - 1 + pkt.dpo", so a value at or past pkt.total moves pkt.dpo + * beyond the reassembled buffer. The transfer still completes: with + * pkt.dpo == pkt.total a closing ETP.DT with dat[0] == 0 evaluates to + * the last in-order packet, so pkt.rx reaches pkt.total and the receiver + * emits its own EOMA. The loopback of that EOMA runs + * j1939_session_completed(), which asks j1939_session_skb_get() for a + * packet past the end of the queued skb, gets NULL, and passes it to + * j1939_sk_recv(). + * + * pkt.total is a packet count, so the last valid packet is + * pkt.total - 1 and the check has to be ">=". + */ + if (dpo >= session->pkt.total) { + if (session->transmission) { + /* A transmitter only builds an EDPO from pkt.tx_acked, + * which comes from the responder's ETP.CM_CTS, so an + * out-of-range value there is an oversized CTS. + */ + j1939_session_cancel(session, J1939_XTP_ABORT_ECTS_TOO_BIG); + } else { + j1939_session_cancel(session, J1939_XTP_ABORT_BAD_EDPO_OFFSET); + } + return; + } + /* transmitted without problems */ - session->pkt.dpo = j1939_etp_ctl_to_packet(skb->data); + session->pkt.dpo = dpo; session->last_cmd = dat[0]; j1939_tp_set_rxtimeout(session, 750); -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH net v7 2/4] can: j1939: validate the EDPO packet number 2026-11-08 1:34 ` [PATCH net v7 2/4] can: j1939: validate the EDPO packet number Chaosheng Qu @ 2026-11-11 7:04 ` sashiko-bot 2026-11-12 21:49 ` netdev-bot+sashiko 1 sibling, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-11-11 7:04 UTC (permalink / raw) To: Chaosheng Qu Cc: linux-can, Marc Kleine-Budde, Oliver Hartkopp, Vincent Mailhol, Oleksij Rempel Thank you for your contribution! Sashiko AI review found 3 potential issues to consider: - [High] can: j1939: missing txtimer cancel before j1939_session_cancel() - [Medium] can: j1939: session ref leak in j1939_session_cancel() on rxtimer race - [Low] can: j1939: missing Fixes trailer for EDPO bounds check -- commit 1f6785035506d85ce93ce6a036332f472884137d Author: Chaosheng Qu <quchaosheng000406@163.com> > can: j1939: validate the EDPO packet number > > ETP.CM_DPO is peer controlled and its packet number is accepted without any > validation. It names the packet a transmission window starts at, and > j1939_xtp_rx_dat_one() places incoming data at "dat[0] - 1 + pkt.dpo", so a > value past the end of the message moves pkt.dpo beyond the reassembled > buffer. > > The transfer still completes normally in that case. With > pkt.dpo == pkt.total, a closing ETP.DT frame with dat[0] == 0 evaluates to > packet pkt.total - 1, which is the last in-order packet, so pkt.rx reaches > pkt.total and the receiver emits its own EOMA. The local loopback of that > EOMA runs j1939_session_completed(), which asks j1939_session_skb_get() for > offset "pkt.dpo * 7" -- one full packet past the end of the queued skb -- > gets NULL, and passes it straight to j1939_sk_recv(), which dereferences > oskb->sk. > > Reject an EDPO that names a packet at or past pkt.total and abort the > session. The check has to be ">=" and not ">": pkt.total is a packet > count, > so the last valid packet is pkt.total - 1. > > The abort code depends on the role. A receiver rejects a bad offset with > J1939_XTP_ABORT_BAD_EDPO_OFFSET, which the comment next to the definition > describes as "Bad EDPO offset" and which had no callers. A transmitter > only > builds an EDPO from pkt.tx_acked, which comes from the responder's > ETP.CM_CTS, so an out-of-range value there is an oversized CTS and is > reported as J1939_XTP_ABORT_ECTS_TOO_BIG. > > Bounding the CTS packet number in j1939_xtp_rx_cts_one() is a separate > change; this one only stops the value from being used as an offset. > > Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com> [Severity: Low] Since this fixes a NULL pointer dereference present since J1939 support was introduced, should it include a Fixes tag: Fixes: 9d71dd0c7009 ("can: add support of SAE J1939 protocol") > diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c > index 590a04c453b56..b0e62822b56e4 100644 > --- a/net/can/j1939/transport.c > +++ b/net/can/j1939/transport.c > @@ -1849,14 +1849,43 @@ static void j1939_xtp_rx_dpo_one(struct j1939_session *session, > struct sk_buff *skb) > { > const u8 *dat = skb->data; > + unsigned int dpo; > > if (j1939_xtp_rx_cmd_bad_pgn(session, skb)) > return; > > netdev_dbg(session->priv->ndev, "%s: 0x%p\n", __func__, session); > > + dpo = j1939_etp_ctl_to_packet(dat); > + > + /* ETP.CM_DPO is peer controlled. j1939_xtp_rx_dat_one() places data at > + * "dat[0] - 1 + pkt.dpo", so a value at or past pkt.total moves pkt.dpo > + * beyond the reassembled buffer. The transfer still completes: with > + * pkt.dpo == pkt.total a closing ETP.DT with dat[0] == 0 evaluates to > + * the last in-order packet, so pkt.rx reaches pkt.total and the receiver > + * emits its own EOMA. The loopback of that EOMA runs > + * j1939_session_completed(), which asks j1939_session_skb_get() for a > + * packet past the end of the queued skb, gets NULL, and passes it to > + * j1939_sk_recv(). > + * > + * pkt.total is a packet count, so the last valid packet is > + * pkt.total - 1 and the check has to be ">=". > + */ > + if (dpo >= session->pkt.total) { > + if (session->transmission) { > + /* A transmitter only builds an EDPO from pkt.tx_acked, > + * which comes from the responder's ETP.CM_CTS, so an > + * out-of-range value there is an oversized CTS. > + */ > + j1939_session_cancel(session, J1939_XTP_ABORT_ECTS_TOO_BIG); > + } else { > + j1939_session_cancel(session, J1939_XTP_ABORT_BAD_EDPO_OFFSET); > + } > + return; > + } [Severity: High] Does aborting here without calling j1939_session_timers_cancel(session) leave session->txtimer armed? j1939_session_cancel() only cancels session->rxtimer, which is why the other receive-path abort handlers (j1939_xtp_rx_cts_one(), j1939_xtp_rx_rts_session_active() and j1939_xtp_rx_dat_one()) call j1939_session_timers_cancel(session) first. On the transmitter path, after j1939_tp_txtimer() sends ETP.CM_DPO with an out-of-range pkt.tx, j1939_session_tx_dat() sees NULL from j1939_session_skb_get_by_offset() and returns -ENOBUFS, so j1939_tp_txtimer() re-arms session->txtimer for 10..25 ms. When the looped-back ETP.CM_DPO then hits this check, session->txtimer stays active on the aborting session, retries up to J1939_XTP_TX_RETRY_LIMIT and overwrites session->err with -ENETUNREACH (or on a receiver session, transmits CTS or EOMA and clobbers the abort timeout via j1939_tp_set_rxtimeout()). [Severity: Medium] Can calling j1939_session_cancel() here race with a second cancel or timeout update to double-arm session->rxtimer and leak a struct j1939_session reference? In j1939_session_cancel() (modified in commit "can: j1939: arm the abort timer under the session list lock"), j1939_session_rxtimer_cancel() runs before taking active_session_list_lock, and then hrtimer_start(&session->rxtimer, ...) runs unconditionally under the lock whenever abort_sent is true or session->state == J1939_SESSION_WAITING_ABORT: CPU0 CPU1 j1939_xtp_rx_dpo_one() j1939_xtp_rx_dpo_one() j1939_session_cancel() j1939_session_cancel() j1939_session_rxtimer_cancel() j1939_session_rxtimer_cancel() j1939_session_list_lock() __j1939_session_cancel() state = WAITING_ABORT j1939_session_get() hrtimer_start(&rxtimer, ...) j1939_session_list_unlock() j1939_session_list_lock() /* state == WAITING_ABORT */ j1939_session_get() hrtimer_start(&rxtimer, ...) <-- overwrites armed timer, leaks ref j1939_tp_set_rxtimeout() also rearms session->rxtimer without holding active_session_list_lock, so it can likewise arm session->rxtimer between j1939_session_rxtimer_cancel() and hrtimer_start(). The leaked kref prevents j1939_session_destroy() from running, leaking the session and its tracked net_device reference and hanging unregister_netdevice(). -- Sashiko AI review · https://sashiko.dev/#/patchset/179410166344.60581.3898011861151549156@163.com?part=2 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v7 2/4] can: j1939: validate the EDPO packet number 2026-11-08 1:34 ` [PATCH net v7 2/4] can: j1939: validate the EDPO packet number Chaosheng Qu 2026-11-11 7:04 ` sashiko-bot @ 2026-11-12 21:49 ` netdev-bot+sashiko 1 sibling, 0 replies; 11+ messages in thread From: netdev-bot+sashiko @ 2026-11-12 21:49 UTC (permalink / raw) To: quchaosheng000406 Cc: robin, o.rempel, socketcan, mkl, kernel, linux-can, linux-kernel, kuba Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 1 · Low: 1 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [Medium] The new EDPO rejection path in j1939_xtp_rx_dpo_one() (net/can/j1939/transport.c) calls j1939_session_cancel() without first… - [Low] The commit fixes a NULL pointer dereference that any remote CAN node can trigger, but it carries no Fixes: tag and no Cc: stable. -- can: j1939: validate the EDPO packet number This patch makes j1939_xtp_rx_dpo_one() reject an ETP.CM_DPO whose packet number is at or past pkt.total, and abort the session when that happens. A receiver aborts with J1939_XTP_ABORT_BAD_EDPO_OFFSET. A transmitter aborts with J1939_XTP_ABORT_ECTS_TOO_BIG. > Signed-off-by: Chaosheng Qu <quchaosheng000406@163.com> [Severity: Low] Should this commit carry a Fixes: tag and Cc: stable? The commit message describes a NULL pointer dereference that a remote CAN node can trigger: j1939_session_completed() se_skb = j1939_session_skb_get(session); <- NULL j1939_sk_recv(session->priv, se_skb); <- dereferences oskb->sk git blame on the baseline shows that j1939_xtp_rx_dpo_one() and j1939_xtp_rx_dat_one() come from commit 9d71dd0c7009 ("can: add support of SAE J1939 protocol"). Would a Fixes: tag for that commit be appropriate? > diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c > index 590a04c453b56..b0e62822b56e4 100644 > --- a/net/can/j1939/transport.c > +++ b/net/can/j1939/transport.c > @@ -1849,14 +1849,43 @@ static void j1939_xtp_rx_dpo_one(struct j1939_session *session, > struct sk_buff *skb) > { > const u8 *dat = skb->data; > + unsigned int dpo; > > if (j1939_xtp_rx_cmd_bad_pgn(session, skb)) > return; > > netdev_dbg(session->priv->ndev, "%s: 0x%p\n", __func__, session); > > + dpo = j1939_etp_ctl_to_packet(dat); > + [ ... ] > + if (dpo >= session->pkt.total) { > + if (session->transmission) { > + /* A transmitter only builds an EDPO from pkt.tx_acked, > + * which comes from the responder's ETP.CM_CTS, so an > + * out-of-range value there is an oversized CTS. > + */ > + j1939_session_cancel(session, J1939_XTP_ABORT_ECTS_TOO_BIG); > + } else { > + j1939_session_cancel(session, J1939_XTP_ABORT_BAD_EDPO_OFFSET); > + } > + return; > + } [Severity: Medium] Should this branch call j1939_session_timers_cancel() before j1939_session_cancel()? The other receive path aborts in this file do: j1939_xtp_rx_cts_one() { ... out_session_cancel: j1939_session_timers_cancel(session); j1939_session_cancel(session, err); } j1939_xtp_rx_dat_one() and both branches of j1939_xtp_rx_rts_session_active() follow the same pattern. j1939_session_cancel() only cancels and re-arms the rxtimer. That means the txtimer can still be pending after this abort. If the txtimer is already running on another CPU, nothing waits for it to finish. On a receiver session, a remote node can send ETP.CM_RTS. In response, j1939_xtp_rx_rts() calls j1939_tp_schedule_txtimer(session, 0) to send the CTS. Suppose the node then immediately sends an ETP.CM_DPO with a packet number >= pkt.total. j1939_xtp_rx_dpo_one() does not check last_cmd, so the frame reaches this branch. This branch returns before last_cmd is updated, so last_cmd is still J1939_ETP_CMD_RTS. When the txtimer then runs: j1939_tp_txtimer() j1939_xtp_txnext_receiver() case J1939_ETP_CMD_RTS: j1939_session_tx_cts() j1939_tp_tx_ctl(session, true, dat); j1939_tp_set_rxtimeout(session, 1250); Does this send a CTS after the abort has already gone out? Does it also replace the abort timeout with a 1250ms rx timeout? j1939_tp_set_rxtimeout() does not take active_session_list_lock. Suppose it runs at the same time as the cancel/get/hrtimer_start sequence in j1939_session_cancel(). Could the rxtimer then be armed twice with two session references taken, leaking one of them? A leaked reference would keep the j1939_priv and netdev references alive, and could hang unregister_netdevice(). The transmitter branch looks like it has the same gap. j1939_tp_txtimer() does not check session->state. On -ENOBUFS it keeps re-arming itself. At the retry limit it does this without the lock: session->err = -ENETUNREACH; j1939_session_rxtimer_cancel(session); j1939_session_deactivate_activate_next(session); That overwrites the abort error and cancels the abort timer. j1939_tp_cmd_recv() sends a frame down the transmitter path whenever j1939_tp_im_transmitter(skcb) is true. Can a frame with a spoofed local SA still reach this branch after the CTS bound is added? Would it help to add j1939_session_timers_cancel(session) before j1939_session_cancel() here? hrtimer_cancel() waits for a running callback, so that would close the window. > + > /* transmitted without problems */ > - session->pkt.dpo = j1939_etp_ctl_to_packet(skb->data); > + session->pkt.dpo = dpo; > session->last_cmd = dat[0]; > j1939_tp_set_rxtimeout(session, 750); -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/179410166344.60581.3898011861151549156%40163.com ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-11-12 21:49 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-08 1:34 [PATCH net v7 0/4] can: j1939: fix the abort timer and bound EDPO Chaosheng Qu 2026-11-08 1:34 ` [PATCH net v7 1/4] can: j1939: arm the abort timer under the session list lock Chaosheng Qu 2026-11-11 6:45 ` sashiko-bot 2026-11-12 21:49 ` netdev-bot+sashiko 2026-11-08 1:34 ` [PATCH net v7 4/4] can: j1939: reject a CTS that names more packets than the message has Chaosheng Qu 2026-11-12 21:49 ` netdev-bot+sashiko 2026-11-08 1:34 ` [PATCH net v7 3/4] can: j1939: drop the message when the reassembled skb is gone Chaosheng Qu 2026-11-12 21:49 ` netdev-bot+sashiko 2026-11-08 1:34 ` [PATCH net v7 2/4] can: j1939: validate the EDPO packet number Chaosheng Qu 2026-11-11 7:04 ` sashiko-bot 2026-11-12 21:49 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox