Use-after-free / RCU + refcount race in l2tp_session_get_by_ifname

HIGH
torvalds/linux
Commit: 05f95729ca84
Affected: <= v7.0-rc6 (net/l2tp: l2tp_session_get_by_ifname)
2026-05-29 00:05 UTC

Description

The commit fixes a potential use-after-free race in l2tp_session_get_by_ifname. Previously, the function took a reference to a session by calling refcount_inc() after matching the session by its ifname. Between the string comparison (strcmp) and the refcount_inc(), another CPU could drop the session's refcount to zero and free the object, yet the reader would continue using the stale pointer. This could result in use-after-free when the caller dereferenced the returned session. The fix switches to refcount_inc_not_zero() and, if the increment cannot be performed (i.e., refcount is zero), it continues walking the list instead of returning a possibly freed object. This brings the ifname getter in line with the other session getters in the file that already use refcount_inc_not_zero().

Proof of Concept

POC (conceptual, not executable): Goal: Reproduce a use-after-free race where a reader returning a session by ifname can be freed concurrently. Setup (conceptual, in a controlled kernel test environment): - Create a tunnel with a session whose ifname is "tun0" and a non-zero refcount (e.g., ref_count = 2 to allow a free path). - The session is placed in tunnel->session_list and is protected by RCU (rcu_read_lock_bh during iteration). Thread A (reader under RCU): - Enter l2tp_session_get_by_ifname(), perform strcmp(session->ifname, "tun0") to locate the session under the idr/RCU walk. - Between the strcmp and taking a reference, Thread B may drop the last reference and schedule the object for kfree_rcu. - In the vulnerable code path (pre-fix), Thread A would execute refcount_inc(&session->ref_count) and then return the raw pointer to the caller, potentially returning a freed object if the refcount had just dropped to zero. Thread B (reaper): - Acquire the last remaining reference to the same session (e.g., via l2tp_session_dec_and_test() or equivalent) and trigger kfree_rcu() to free the object after the current grace period. Race scenario (pre-fix): 1) Thread A matches the session by ifname under RCU read lock. 2) Thread B drops the last reference and schedules kfree_rcu() for the session. 3) Thread A calls refcount_inc(&session->ref_count) while the refcount is already zero due to Step 2. 4) refcount_inc() detects addition on 0, emits the kernel warning ("refcount_t: addition on 0; use-after-free"), and returns a pointer that may now be freed or reused. 5) Thread A proceeds to use the returned pointer, risking use-after-free or memory corruption. Fix applied by the commit (conceptual): - Replace refcount_inc(&session->ref_count) with refcount_inc_not_zero(&session->ref_count). - If the increment cannot be performed (returns 0), continue walking the list instead of returning the pointer. - This mirrors the behavior of the other session getters in the file and avoids handing out a possibly freed object. Impact of PoC: In a system affected by the pre-fix code, Step 4 would expose a use-after-free condition. With the fix, the reader would skip that session and continue the search, avoiding returning a freed object. Notes: - The PoC is conceptual and intended to illustrate the race; implementing a reliable kernel-space PoC requires a controlled environment and careful synchronization, which is not advisable to publish as runnable code here. The kernel’s own debug messaging (refcount_t: addition on 0; use-after-free) would indicate the race in a crash log or dmesg, aiding diagnosis.

Commit Details

Author: Michael Bommarito

Date: 2026-05-23 02:34 UTC

Message:

l2tp: use refcount_inc_not_zero in l2tp_session_get_by_ifname 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.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>

Triage Assessment

Vulnerability Type: Memory safety (use-after-free)

Confidence: HIGH

Reasoning:

The change fixes a potential use-after-free race between a reader inside an RCU section and concurrent refcount/deallocation. By switching to refcount_inc_not_zero() and continuing the walk on failure, it prevents returning a session whose reference count could be zero, which could lead to use-after-free and potential memory safety issues.

Verification Assessment

Vulnerability Type: Use-after-free / RCU + refcount race in l2tp_session_get_by_ifname

Confidence: HIGH

Affected Versions: <= v7.0-rc6 (net/l2tp: l2tp_session_get_by_ifname)

Code Diff

diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index 1455f67e01ddb8..9419c8555d2290 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -441,12 +441,13 @@ struct l2tp_session *l2tp_session_get_by_ifname(const struct net *net, idr_for_each_entry_ul(&pn->l2tp_tunnel_idr, tunnel, tmp, tunnel_id) { if (tunnel) { list_for_each_entry_rcu(session, &tunnel->session_list, list) { - if (!strcmp(session->ifname, ifname)) { - refcount_inc(&session->ref_count); - rcu_read_unlock_bh(); + if (strcmp(session->ifname, ifname)) + continue; + if (!refcount_inc_not_zero(&session->ref_count)) + continue; + rcu_read_unlock_bh(); - return session; - } + return session; } } }
← Back to Alerts View on GitHub →