From: Michael Bommarito <michael.bommarito@gmail.com> stable inclusion from stable-v6.12.93 commit ee80455feffb9cb62b5b58715cabeff495e666b2 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/16246 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=... -------------------------------- commit 05f95729ca844704d15e49ce14868af4b403b32b upstream. A reader in l2tp_session_get_by_ifname() can return a pointer to a session whose refcount has reached zero. The getter takes its reference with plain refcount_inc(), but every other session getter in the same file (l2tp_v2_session_get, l2tp_v3_session_get, and the corresponding _get_next variants) uses refcount_inc_not_zero() because the IDR/RCU lookup can race with refcount_dec_and_test() -> l2tp_session_free() -> kfree_rcu(). The ifname getter is the only outlier; the inconsistency was raised on-list after 979c017803c4 ("l2tp: use list_del_rcu in l2tp_session_unhash"). A reader inside rcu_read_lock_bh() that matches session->ifname can be preempted between the strcmp() and the refcount_inc(). If the last reference drops on another CPU in that window, the reader's refcount_inc() runs on a counter that has reached zero. refcount_t catches the addition-on-zero, prints "refcount_t: addition on 0; use-after-free", saturates the counter, and returns the saturated pointer to the caller. Session memory is held live by the in-flight RCU read section, but the kfree_rcu() callback queued from l2tp_session_free() will free it once the grace period closes; a caller that dereferences the returned session past that point hits a slab-use-after-free. On PREEMPT_RT local_bh_disable() is a per-CPU sleeping lock and the preemption window is real; on stock PREEMPT kernels local_bh_disable() is a preempt_count increment that closes the cross-CPU race in practice (see below). Use refcount_inc_not_zero() and continue the list walk on failure, matching the other session getters in the file. The ifname getter is the only session getter in net/l2tp/ that still uses the bare refcount_inc() pattern; this change restores file-internal consistency. The success path is unchanged. Fixes: abe7a1a7d0b6 ("l2tp: improve tunnel/session refcount helpers") Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com> Reviewed-by: James Chapman <jchapman@katalix.com> Reviewed-by: Simon Horman <horms@kernel.org> Link: https://patch.msgid.link/20260523023423.2568972-1-michael.bommarito@gmail.co... Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Conflicts: net/l2tp/l2tp_core.c [conflicts due to not mergered prerequisites, commit abe7a1a7d0b6 ("l2tp: improve tunnel/session refcount helpers") and commit 1f4c3dce9112 ("l2tp: use get_next APIs for management requests and procfs/debugfs") and commit 8c6245af4fc5 ("l2tp: drop the now unused l2tp_tunnel_get_session") has not been merged, fixed the same issue in older versions.] Signed-off-by: Gaosheng Cui <cuigaosheng1@huawei.com> --- net/l2tp/l2tp_core.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index c94051239670..0a6369c70ff4 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -256,9 +256,9 @@ struct l2tp_session *l2tp_tunnel_get_session(struct l2tp_tunnel *tunnel, rcu_read_lock_bh(); hlist_for_each_entry_rcu(session, session_list, hlist) if (session->session_id == session_id) { - l2tp_session_inc_refcount(session); + if (!refcount_inc_not_zero(&session->ref_count)) + continue; rcu_read_unlock_bh(); - return session; } rcu_read_unlock_bh(); @@ -277,9 +277,9 @@ struct l2tp_session *l2tp_session_get(const struct net *net, u32 session_id) rcu_read_lock_bh(); hlist_for_each_entry_rcu(session, session_list, global_hlist) if (session->session_id == session_id) { - l2tp_session_inc_refcount(session); + if (!refcount_inc_not_zero(&session->ref_count)) + continue; rcu_read_unlock_bh(); - return session; } rcu_read_unlock_bh(); @@ -298,7 +298,8 @@ struct l2tp_session *l2tp_session_get_nth(struct l2tp_tunnel *tunnel, int nth) for (hash = 0; hash < L2TP_HASH_SIZE; hash++) { hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) { if (++count > nth) { - l2tp_session_inc_refcount(session); + if (!refcount_inc_not_zero(&session->ref_count)) + continue; rcu_read_unlock_bh(); return session; } @@ -324,12 +325,12 @@ struct l2tp_session *l2tp_session_get_by_ifname(const struct net *net, rcu_read_lock_bh(); for (hash = 0; hash < L2TP_HASH_SIZE_2; hash++) { hlist_for_each_entry_rcu(session, &pn->l2tp_session_hlist[hash], global_hlist) { - if (!strcmp(session->ifname, ifname)) { - l2tp_session_inc_refcount(session); - rcu_read_unlock_bh(); - - return session; - } + if (strcmp(session->ifname, ifname)) + continue; + if (!refcount_inc_not_zero(&session->ref_count)) + continue; + rcu_read_unlock_bh(); + return session; } } -- 2.34.1