From: Marc Kleine-Budde <mkl@pengutronix.de>
To: Tetsuo Handa <penguin-kernel@i-love.sakura.ne.jp>
Cc: linux-can@vger.kernel.org,
syzbot+e2af46126e0644cbebdd@syzkaller.appspotmail.com,
Oleksij Rempel <o.rempel@pengutronix.de>
Subject: Re: [PATCH net 06/22] can: j1939: j1939_sk_bind(): fix j1939_ecu leak when re-bind failed
Date: Tue, 29 Sep 2026 10:56:07 +0200 [thread overview]
Message-ID: <20260929-private-shellfish-from-mars-caa38b-mkl@pengutronix.de> (raw)
In-Reply-To: <20260928193312.553632-7-mkl@pengutronix.de>
[-- Attachment #1: Type: text/plain, Size: 10912 bytes --]
On 28.09.2026 20:45:13, Marc Kleine-Budde wrote:
> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
>
> syzbot is reporting "struct j1939_ecu" refcount leak, which occurs when
> netdev_hold() is called during ECU creation but the corresponding
> netdev_put() is never executed because the parent "struct j1939_ecu"
> object is leaked.
>
> unregister_netdevice: waiting for vxcan1 to become free. Usage count = 3
> ref_tracker: netdev@ffff8880710f0700 has 1/2 users at
> __netdev_tracker_alloc include/linux/netdevice.h:4496 [inline]
> netdev_hold include/linux/netdevice.h:4525 [inline]
> j1939_ecu_create_locked+0x1c9/0x400 net/can/j1939/bus.c:159
> j1939_local_ecu_get+0xeb/0x220 net/can/j1939/bus.c:293
> j1939_sk_bind+0x70a/0xc60 net/can/j1939/socket.c:529
> __sys_bind_socket net/socket.c:1920 [inline]
> __sys_bind+0x2e3/0x410 net/socket.c:1951
> __do_sys_bind net/socket.c:1956 [inline]
> __se_sys_bind net/socket.c:1954 [inline]
> __x64_sys_bind+0x7a/0x90 net/socket.c:1954
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> ref_tracker: netdev@ffff8880710f0700 has 1/2 users at
> __netdev_tracker_alloc include/linux/netdevice.h:4496 [inline]
> netdev_hold include/linux/netdevice.h:4525 [inline]
> j1939_priv_create net/can/j1939/main.c:140 [inline]
> j1939_netdev_start+0x387/0xb20 net/can/j1939/main.c:268
> j1939_sk_bind+0x946/0xc60 net/can/j1939/socket.c:506
> __sys_bind_socket net/socket.c:1920 [inline]
> __sys_bind+0x2e3/0x410 net/socket.c:1951
> __do_sys_bind net/socket.c:1956 [inline]
> __se_sys_bind net/socket.c:1954 [inline]
> __x64_sys_bind+0x7a/0x90 net/socket.c:1954
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> The root cause lies in the error handling of j1939_sk_bind() during a
> re-bind operation (binding an already bound socket to the same interface).
> Currently, the function prematurely drops the old ECU references by calling
> j1939_local_ecu_put() before verifying whether the new configuration can be
> successfully acquired via j1939_local_ecu_get().
>
> If j1939_local_ecu_get() subsequently fails, the function unconditionally
> calls j1939_netdev_stop() and clears jsk->priv. This leaves the socket in a
> half-broken state where the old ECU's refcount has already been decremented
> incompletely, but the socket destruct pathway (j1939_sk_sock_destruct) can
> no longer perform proper cleanup because jsk->priv is NULL. As a result,
> the old "struct j1939_ecu" remains orphaned on the priv->ecus list,
> permanently leaking both the ECU object and the net_device reference held
> inside it.
>
> Fix this by deferring the removal and release of the old ECU references
> until after j1939_local_ecu_get() has successfully acquired the new
> resources. As a side effect of this change, the socket's state no longer
> changes when the re-bind operation failed.
>
> Reported-by: syzbot+e2af46126e0644cbebdd@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=e2af46126e0644cbebdd
> Assisted-by: Gemini-Pro
> Fixes: f214744c8a27 ("can: j1939: j1939_sk_bind(): call j1939_priv_put() immediately when j1939_local_ecu_get() failed")
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
> Link: https://patch.msgid.link/deb3ac27-5eaf-406a-9bf4-733cc43ecaba@I-love.SAKURA.ne.jp
> Cc: stable@vger.kernel.org
> Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
I've dropped the patch for now, the netdev bots gave the following
review:
regards,
Marc
--- Patch [6]: [PATCH net 06/22] can: j1939: j1939_sk_bind(): fix j1939_ecu leak when re-bind failed ---
commit 465afca7f35626e9f75e201bea23194ff9fbfa18
Author: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
can: j1939: j1939_sk_bind(): fix j1939_ecu leak when re-bind failed
This patch reorders j1939_sk_bind() so it takes the new ECU references
with j1939_local_ecu_get() before it drops the old ones. On a re-bind,
the old references are dropped only after the get succeeds. A failed
re-bind now leaves the socket state unchanged.
Link: https://patch.msgid.link/deb3ac27-5eaf-406a-9bf4-733cc43ecaba@I-love.SAKURA.ne.jp
> diff --git a/net/can/j1939/socket.c b/net/can/j1939/socket.c
> index 50a598ef5fd4..24efb25c58c3 100644
> --- a/net/can/j1939/socket.c
> +++ b/net/can/j1939/socket.c
[ ... ]
> @@ -519,22 +517,31 @@ static int j1939_sk_bind(struct socket *sock, struct sockaddr_unsized *uaddr, in
> jsk->priv = priv;
> }
>
> + /* get new references without dropping old references */
> + ret = j1939_local_ecu_get(priv, addr->can_addr.j1939.name, addr->can_addr.j1939.addr);
> + if (ret) {
> + /* nothing to undo if re-bind() failed */
> + if (!was_bound) {
> + j1939_netdev_stop(priv);
> + jsk->priv = NULL;
> + synchronize_rcu();
> + j1939_priv_put(priv);
> + }
> + goto out_release_sock;
> + }
Discovered by: gpt-6-astra, opus-5-5 · Missed by: sonnet-5
The commit message says:
If j1939_local_ecu_get() subsequently fails, the function unconditionally
calls j1939_netdev_stop() and clears jsk->priv. This leaves the socket in a
half-broken state where the old ECU's refcount has already been decremented
incompletely, but the socket destruct pathway (j1939_sk_sock_destruct) can
no longer perform proper cleanup because jsk->priv is NULL.
Is this description accurate? In the old failed re-bind path, the
references look balanced:
first bind():
j1939_netdev_start() +1 priv kref, +1 rx_kref
j1939_priv_get() +1 priv (jsk->priv)
j1939_local_ecu_get() +1 ecu kref, nusers++
j1939_jsk_add() +1 priv
old failing re-bind():
j1939_jsk_del() -1 priv
j1939_local_ecu_put() nusers--, j1939_ecu_put()
j1939_local_ecu_get() fails, undoes its own nusers++
j1939_netdev_stop() -1 rx_kref (__j1939_rx_release()), -1 priv
j1939_priv_put() -1 priv (the jsk->priv reference)
j1939_sk_sock_destruct() only does this:
if (jsk->priv) {
j1939_priv_put(jsk->priv);
jsk->priv = NULL;
}
The error path has already made that put, so clearing jsk->priv doesn't
seem to skip anything. The destructor also has no ECU cleanup.
j1939_sk_netdev_event_unregister() runs the same sequence (jsk_del,
local_ecu_put, netdev_stop, priv_put, jsk->priv = NULL) and treats it as
balanced.
The Fixes: tag also looks off. f214744c8a27 only added the paired
jsk->priv = NULL / synchronize_rcu() / j1939_priv_put(). It doesn't
appear to have introduced an ECU imbalance.
If that's right, the reordering is fine as a behaviour improvement. Can
it fix the leak in the syzbot report, though?
Could the leak come from ecu->ac_timer instead? j1939_ac_process() arms
the timer with j1939_ecu_timer_start(), which takes an ECU reference for
250 ms. This also happens for the TX echo of an address claim that the
bound socket sent itself. Nothing cancels ac_timer at teardown.
j1939_ecu_timer_cancel() is only called from j1939_ac_process().
__j1939_rx_release() does:
j1939_can_rx_unregister(priv);
j1939_ecu_unmap_all(priv);
j1939_priv_set(priv->ndev, NULL);
If the timer fires after that, j1939_ecu_timer_handler() maps the ECU
again:
write_lock_bh(&priv->lock);
...
j1939_ecu_map_locked(ecu);
...
j1939_ecu_put(ecu);
j1939_ecu_map_locked() takes a new ECU reference and stores it in
priv->ents[]. Nothing will unmap it later, because priv is now detached
from the netdev.
The ECU then pins priv and its netdev tracker, and priv pins its own
dev_tracker. That matches the two ref_tracker entries in the report
(j1939_ecu_create_locked() and j1939_priv_create()).
The sequence would be bind(), send an address claim, then close() within
250 ms via j1939_sk_release()->j1939_netdev_stop(). That still works
after this patch. I haven't confirmed that this is the exact syzbot
path.
Would it make sense to revisit the root cause before this goes to
stable? For example, pending ac_timers could be cancelled in
__j1939_rx_release(), or mapping could be refused after the rx release.
> +
> + /* drop old references after re-bind() succeeded */
> + if (was_bound) {
> + j1939_jsk_del(priv, jsk);
> + j1939_local_ecu_put(priv, jsk->addr.src_name, jsk->addr.sa);
> + }
Discovered by: opus-5-5, sonnet-5 · Missed by: gpt-6-astra
After this reordering, no j1939_sk_bind() path ends with the old
references dropped and no new ones taken. Is this comment in
j1939_sk_netdev_event_unregister() now stale?
net/can/j1939/socket.c:j1939_sk_netdev_event_unregister() {
...
if (jsk->state & J1939_SOCK_BOUND) {
/* Neither j1939_sk_bind() nor j1939_sk_release() called j1939_jsk_del().
* Make this socket no longer bound, by pretending as if j1939_sk_bind()
* dropped old references but did not get new references.
*/
...
}
It could instead describe the teardown directly, for example as what
j1939_sk_release() does plus clearing jsk->priv.
> +
> /* set default transmit pgn */
> if (j1939_pgn_is_valid(addr->can_addr.j1939.pgn))
> jsk->pgn_rx_filter = addr->can_addr.j1939.pgn;
Discovered by: gpt-6-astra, opus-5-5 · Missed by: sonnet-5 · Called a false positive by: gpt-6-astra
This isn't a bug introduced by this patch, but it is in the code being
restructured here. jsk->pgn_rx_filter is written only when the new PGN
is valid. There is no else branch, and only j1939_sk_init() resets the
filter to J1939_NO_PGN.
Documentation/networking/j1939.rst documents .pgn = J1939_NO_PGN as the
way "to disable bind() rx filter for PGN".
Take a socket bound with a valid PGN and then re-bound with J1939_NO_PGN.
Will j1939_sk_recv_match_one() keep dropping frames because of the old
filter?
if (j1939_pgn_is_valid(jsk->pgn_rx_filter) &&
jsk->pgn_rx_filter != skcb->addr.pgn)
return false;
Also, the comment says "set default transmit pgn", but the code sets the
rx filter. The transmit PGN is jsk->addr.pgn, which j1939_sk_connect()
sets.
> jsk->addr.src_name = addr->can_addr.j1939.name;
> jsk->addr.sa = addr->can_addr.j1939.addr;
[ ... ]
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
next prev parent reply other threads:[~2026-09-29 8:56 UTC|newest]
Thread overview: 38+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 18:45 [PATCH net 0/22] pull-request: can 2026-09-28 Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 01/22] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices Marc Kleine-Budde
2026-09-28 19:36 ` netdev-bot+sinfo
2026-09-28 18:45 ` [PATCH net 02/22] can: skb: make echo skb freeing safe in any IRQ context Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 03/22] can: skb: make CAN skb allocation failure paths IRQ-safe Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 04/22] can: dev: can_put_echo_skb(): free skb on invalid echo index Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 05/22] can: dev: init_can_skb(): restore skb header initialization Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 06/22] can: j1939: j1939_sk_bind(): fix j1939_ecu leak when re-bind failed Marc Kleine-Budde
2026-09-29 8:56 ` Marc Kleine-Budde [this message]
2026-09-29 9:07 ` Tetsuo Handa
2026-09-29 9:39 ` Marc Kleine-Budde
2026-09-29 9:48 ` Tetsuo Handa
2026-09-29 9:53 ` Marc Kleine-Budde
2026-09-29 9:46 ` Marc Kleine-Budde
2026-10-04 11:44 ` Tetsuo Handa
2026-09-28 18:45 ` [PATCH net 07/22] can: isotp: check the frame type, not just the length Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 08/22] can: cc770: platform_get_irq(): propagate the error Marc Kleine-Budde
2026-09-29 19:33 ` sashiko-bot
2026-09-28 18:45 ` [PATCH net 09/22] can: cc770: fix the clock divider check on the platform bus Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 10/22] can: kvaser_pciefd: fix use-after-free in bec poll timer Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 11/22] can: m_can: pci: add missing pm_runtime_dont_use_autosuspend() call Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 12/22] can: m_can: m_can_class_suspend(): fix suspend deinit() error path Marc Kleine-Budde
2026-09-29 19:33 ` sashiko-bot
2026-09-29 20:34 ` Marc Kleine-Budde
2026-10-01 12:44 ` Markus Schneider-Pargmann
2026-10-01 12:57 ` Oliver Hartkopp
2026-09-28 18:45 ` [PATCH net 13/22] can: sun4i_can: sun4ican_probe(): fix clk leak Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 14/22] can: xilinx_can: set CAN FD flags on received frames Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 15/22] can: mcp251xfd: mcp251xfd_probe(): reject devices without match data Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 16/22] can: hi311x: drop hi3110_lock before free_irq() on open failure Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 17/22] can: ems_usb: use usb_kill_urb() to stop the intr URB Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 18/22] usb: f81604: fix struct f81604_int_data size mismatch Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 19/22] can: gs_usb: kill RX URBs before destroying the netdevs Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 20/22] can: gs_usb: add workarounds for HScanT USB to CAN adapter Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 21/22] can: kvaser_usb: validate command format before parsing in hydra receive path Marc Kleine-Budde
2026-09-28 18:45 ` [PATCH net 22/22] can: peak_usb: fix missing CAN_ERR_FLAG when reporting error counters Marc Kleine-Budde
2026-09-29 1:09 ` [PATCH net 0/22] pull-request: can 2026-09-28 Jakub Kicinski
2026-09-29 21:12 ` 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=20260929-private-shellfish-from-mars-caa38b-mkl@pengutronix.de \
--to=mkl@pengutronix.de \
--cc=linux-can@vger.kernel.org \
--cc=o.rempel@pengutronix.de \
--cc=penguin-kernel@i-love.sakura.ne.jp \
--cc=syzbot+e2af46126e0644cbebdd@syzkaller.appspotmail.com \
/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