From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 463AA3EF674; Tue, 24 Mar 2026 11:19:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774351186; cv=none; b=a/RGeDfrXFxYjkJ4a1/knH1cWoDZ3eGFpPWm6tNL8PtHYYIof3pqg3D1/MEaHBUG5YWq93oDjRpMO55QgbG9bDNdaxevhG9vuVgI2nDrqZ7r5F7Kihg8DdQqFSWNLFpaw8ays+VB5XhFY5wqsVyU2RxjHtE4iqsiVehvYNCYPfI= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774351186; c=relaxed/simple; bh=J6CsZUm2/SZdVxQCBkse0l2OsysnPlRn6hCIb5DNLVM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=DbFlVIvGvGV/UWlHmUvF5cSUxG/KOaFFVQjGrjLWaGyVBNVyOESZ+FmD6ug7QqAh1wCgJ+fEaMCv5B+8PA5I8LK+tw83JZGixtIti9lFXHIQKS3ELDEkcowYOJvGQ/nPozzAcqSKR9N5fUvun2OMetEKl/efvFTT16iOugOWj/U= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=AOn0BW3n; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="AOn0BW3n" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A7AA8C2BCB1; Tue, 24 Mar 2026 11:19:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774351185; bh=J6CsZUm2/SZdVxQCBkse0l2OsysnPlRn6hCIb5DNLVM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AOn0BW3nUi40AYGh070X0D2h41KXwfYYMYPW821iO5GFKvgkdPElRTV4eYB4dCU/5 P+DkeY4jRE28UMCZHSm0MFSBr8q2+2dAllflY+JrlNVZfyK1VzrqppD2ycT6YiYFJ0 mwifNxwgsx8WSnwOUHVPOyN66l9TMj7uMbsJfZXt7VQUeIAWWWrwJ/+kgJl5XzcLUP vuxqP7aCmTc8nu/zLsbgHjpEl7IFC+S5Wh7EFTemhjty+hh89z10k61Jp028S07WqY LV3rFMzfocq8ClOh0UougFSYfR8u+ZdB7YV86b/XwtCbz+YfxgrITn3/KtHJUfV0tC APa/PLbjq6iAw== From: Sasha Levin To: patches@lists.linux.dev, stable@vger.kernel.org Cc: Deepanshu Kartikey , syzbot+f50072212ab792c86925@syzkaller.appspotmail.com, Eric Dumazet , Jakub Kicinski , Sasha Levin , davem@davemloft.net, pabeni@redhat.com, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH AUTOSEL 6.19-5.10] atm: lec: fix use-after-free in sock_def_readable() Date: Tue, 24 Mar 2026 07:19:18 -0400 Message-ID: <20260324111931.3257972-9-sashal@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260324111931.3257972-1-sashal@kernel.org> References: <20260324111931.3257972-1-sashal@kernel.org> Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-stable: review X-Patchwork-Hint: Ignore X-stable-base: Linux 6.19.9 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From: Deepanshu Kartikey [ Upstream commit 922814879542c2e397b0e9641fd36b8202a8e555 ] A race condition exists between lec_atm_close() setting priv->lecd to NULL and concurrent access to priv->lecd in send_to_lecd(), lec_handle_bridge(), and lec_atm_send(). When the socket is freed via RCU while another thread is still using it, a use-after-free occurs in sock_def_readable() when accessing the socket's wait queue. The root cause is that lec_atm_close() clears priv->lecd without any synchronization, while callers dereference priv->lecd without any protection against concurrent teardown. Fix this by converting priv->lecd to an RCU-protected pointer: - Mark priv->lecd as __rcu in lec.h - Use rcu_assign_pointer() in lec_atm_close() and lecd_attach() for safe pointer assignment - Use rcu_access_pointer() for NULL checks that do not dereference the pointer in lec_start_xmit(), lec_push(), send_to_lecd() and lecd_attach() - Use rcu_read_lock/rcu_dereference/rcu_read_unlock in send_to_lecd(), lec_handle_bridge() and lec_atm_send() to safely access lecd - Use rcu_assign_pointer() followed by synchronize_rcu() in lec_atm_close() to ensure all readers have completed before proceeding. This is safe since lec_atm_close() is called from vcc_release() which holds lock_sock(), a sleeping lock. - Remove the manual sk_receive_queue drain from lec_atm_close() since vcc_destroy_socket() already drains it after lec_atm_close() returns. v2: Switch from spinlock + sock_hold/put approach to RCU to properly fix the race. The v1 spinlock approach had two issues pointed out by Eric Dumazet: 1. priv->lecd was still accessed directly after releasing the lock instead of using a local copy. 2. The spinlock did not prevent packets being queued after lec_atm_close() drains sk_receive_queue since timer and workqueue paths bypass netif_stop_queue(). Note: Syzbot patch testing was attempted but the test VM terminated unexpectedly with "Connection to localhost closed by remote host", likely due to a QEMU AHCI emulation issue unrelated to this fix. Compile testing with "make W=1 net/atm/lec.o" passes cleanly. Reported-by: syzbot+f50072212ab792c86925@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f50072212ab792c86925 Link: https://lore.kernel.org/all/20260309093614.502094-1-kartikey406@gmail.com/T/ [v1] Signed-off-by: Deepanshu Kartikey Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260309155908.508768-1-kartikey406@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- LLM Generated explanations, may be completely bogus: None of these are specific to the ATM LEC `priv->lecd` race. No prior fix for this specific bug exists. Record: No prior fix for this specific bug in stable. ## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT ### Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY - **Subsystem:** net/atm (ATM networking, LAN Emulation Client) - **Criticality:** PERIPHERAL — ATM is a legacy networking technology, but it's still used in some DSL/broadband environments and the code is compiled into many kernel configs ### Step 7.2: ASSESS SUBSYSTEM ACTIVITY Very low activity (legacy subsystem), but bugs still get fixed when found. The fact that syzbot can trigger it means the code is reachable and exercised by kernel fuzzing. Record: [net/atm] [PERIPHERAL but still reachable and fuzzed] [Legacy but compiled into many configs] ## PHASE 8: IMPACT AND RISK ASSESSMENT ### Step 8.1: DETERMINE WHO IS AFFECTED Users with ATM/LEC networking configured (CONFIG_ATM_LANE). This is a legacy technology but still compiled in many distribution kernels. Record: Users with ATM LANE support configured. Narrower user base but still present in many distro configs. ### Step 8.2: DETERMINE THE TRIGGER CONDITIONS - Race between closing the ATM LEC daemon socket and concurrent network operations (transmit, bridge handling, ARP) - Syzbot triggers it via IPv6 MLD workqueue → packet transmission path - Can be triggered from userspace (syzbot confirms reproducibility) - Timing-dependent race but with a real race window Record: Userspace-triggerable race condition. Reproducible by syzbot fuzzer. ### Step 8.3: DETERMINE THE FAILURE MODE SEVERITY - **Use-after-free** detected by KASAN - Can cause: kernel crash/oops, potential memory corruption, potentially exploitable - UAFs are among the most dangerous bug classes — they can lead to privilege escalation - **Severity: CRITICAL** (UAF with userspace trigger) Record: [KASAN slab-use-after-free] [Severity: CRITICAL — UAF with userspace trigger, crash/corruption/potential exploit] ### Step 8.4: CALCULATE RISK-BENEFIT RATIO **BENEFIT:** High - Fixes a syzbot-confirmed UAF that affects all stable trees (5.4+) - Prevents kernel crash/corruption - Userspace-triggerable = security relevant - Well-reviewed by top networking expert **RISK:** Low-Medium - ~50 lines changed, but all within a single well-contained pattern (RCU conversion) - Textbook RCU pattern, well-understood - Reviewed by Eric Dumazet - synchronize_rcu() is safe in the calling context (sleeping lock held) - Low file churn means clean backport likely Record: [Benefit: HIGH — UAF fix, all stable trees affected, security- relevant] [Risk: LOW — textbook RCU, expert-reviewed, well-contained] [Ratio: Strongly favorable] ## PHASE 9: FINAL SYNTHESIS ### Step 9.1: COMPILE THE EVIDENCE **Evidence FOR backporting:** 1. Fixes a confirmed use-after-free (KASAN slab-use-after-free in sock_def_readable) 2. Reported by syzbot — reproducible with concrete trigger 3. Affects ALL active stable trees (5.4, 5.10, 5.15, 6.1, 6.6) 4. Userspace-triggerable race condition = security-relevant 5. Reviewed-by Eric Dumazet (top networking expert) 6. Committed by Jakub Kicinski (net maintainer) 7. v2 after expert review iteration — fix quality is high 8. Textbook RCU pattern — well-understood, low regression risk 9. Well-contained to single file + header 10. No prerequisites or dependencies 11. Low-churn file — clean backport expected 12. send_to_lecd() has 11 call sites — wide impact surface **Evidence AGAINST backporting:** 1. ~50 lines of change (moderate but not huge) 2. ATM/LEC is a legacy subsystem with narrower user base 3. Syzbot VM testing was inconclusive (QEMU issue, not fix issue) 4. No explicit Cc: stable (expected for candidates under review) **UNRESOLVED QUESTIONS:** - None significant. All claims verified through git blame, syzbot report, and mailing list discussion. ### Step 9.2: APPLY THE STABLE RULES CHECKLIST 1. **Obviously correct and tested?** YES — textbook RCU conversion, reviewed by Eric Dumazet, compile-tested 2. **Fixes a real bug that affects users?** YES — syzbot-confirmed UAF, all stable trees affected 3. **Important issue?** YES — UAF (crash, corruption, potential security exploit) 4. **Small and contained?** YES — 2 files, ~50 lines, single pattern conversion 5. **No new features or APIs?** CORRECT — pure bug fix 6. **Can apply to stable trees?** YES — low-churn file, should apply cleanly ### Step 9.3: CHECK FOR EXCEPTION CATEGORIES Not an exception category — this is a straightforward bug fix, the primary stable material. ### Step 9.4: DECISION This is a clear YES. A syzbot-confirmed use-after-free affecting all active stable trees, with a well-reviewed RCU-based fix from expert networking developers. The fix is textbook, contained, and has no dependencies. ## Verification - [Phase 1] Parsed tags: Reported-by: syzbot, Reviewed-by: Eric Dumazet, Signed-off-by: Jakub Kicinski (net maintainer). v2 after expert review. - [Phase 2] Diff analysis: +48/-26 lines across 7 functions in lec.c + 1 line in lec.h. All changes convert bare priv->lecd access to RCU- protected access (rcu_read_lock/rcu_dereference/rcu_access_pointer/rcu _assign_pointer/synchronize_rcu). - [Phase 3] git blame: Buggy code dates to initial git import (2005, ^1da177e4c3f41) and 2006 ATM rework (d44f77466cfdc6). Present in ALL stable trees. - [Phase 3] git log: File has only 15 commits since 2020, very low churn. No prerequisites needed. - [Phase 3] Author has 10+ bug-fix commits across multiple subsystems. Fix endorsed by Eric Dumazet and Jakub Kicinski. - [Phase 4] Syzbot report confirmed: KASAN slab-use-after-free in sock_def_readable. Affected versions: 5.4, 5.10, 5.15, 6.1, 6.6. - [Phase 4] Lore discussion: v2 patch, no NAKs, Eric Dumazet gave Reviewed-by after v1 issues fixed. - [Phase 5] send_to_lecd() has 11 call sites in lec.c. lec_handle_bridge() called from transmit path. Bug is userspace- reachable (confirmed by syzbot IPv6 MLD trigger). - [Phase 6] Bug exists in all active stable trees. No prior fix for this specific race. Clean backport expected. - [Phase 7] net/atm is legacy but compiled in many distro configs and exercised by syzbot. - [Phase 8] Failure mode: UAF → crash/corruption/potential exploit. Severity: CRITICAL. Benefit: HIGH, Risk: LOW. **YES** net/atm/lec.c | 72 +++++++++++++++++++++++++++++++++------------------ net/atm/lec.h | 2 +- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/net/atm/lec.c b/net/atm/lec.c index c39dc5d367979..b6f764e524f7c 100644 --- a/net/atm/lec.c +++ b/net/atm/lec.c @@ -154,10 +154,19 @@ static void lec_handle_bridge(struct sk_buff *skb, struct net_device *dev) /* 0x01 is topology change */ priv = netdev_priv(dev); - atm_force_charge(priv->lecd, skb2->truesize); - sk = sk_atm(priv->lecd); - skb_queue_tail(&sk->sk_receive_queue, skb2); - sk->sk_data_ready(sk); + struct atm_vcc *vcc; + + rcu_read_lock(); + vcc = rcu_dereference(priv->lecd); + if (vcc) { + atm_force_charge(vcc, skb2->truesize); + sk = sk_atm(vcc); + skb_queue_tail(&sk->sk_receive_queue, skb2); + sk->sk_data_ready(sk); + } else { + dev_kfree_skb(skb2); + } + rcu_read_unlock(); } } #endif /* IS_ENABLED(CONFIG_BRIDGE) */ @@ -216,7 +225,7 @@ static netdev_tx_t lec_start_xmit(struct sk_buff *skb, int is_rdesc; pr_debug("called\n"); - if (!priv->lecd) { + if (!rcu_access_pointer(priv->lecd)) { pr_info("%s:No lecd attached\n", dev->name); dev->stats.tx_errors++; netif_stop_queue(dev); @@ -449,10 +458,19 @@ static int lec_atm_send(struct atm_vcc *vcc, struct sk_buff *skb) break; skb2->len = sizeof(struct atmlec_msg); skb_copy_to_linear_data(skb2, mesg, sizeof(*mesg)); - atm_force_charge(priv->lecd, skb2->truesize); - sk = sk_atm(priv->lecd); - skb_queue_tail(&sk->sk_receive_queue, skb2); - sk->sk_data_ready(sk); + struct atm_vcc *vcc; + + rcu_read_lock(); + vcc = rcu_dereference(priv->lecd); + if (vcc) { + atm_force_charge(vcc, skb2->truesize); + sk = sk_atm(vcc); + skb_queue_tail(&sk->sk_receive_queue, skb2); + sk->sk_data_ready(sk); + } else { + dev_kfree_skb(skb2); + } + rcu_read_unlock(); } } #endif /* IS_ENABLED(CONFIG_BRIDGE) */ @@ -468,23 +486,16 @@ static int lec_atm_send(struct atm_vcc *vcc, struct sk_buff *skb) static void lec_atm_close(struct atm_vcc *vcc) { - struct sk_buff *skb; struct net_device *dev = (struct net_device *)vcc->proto_data; struct lec_priv *priv = netdev_priv(dev); - priv->lecd = NULL; + rcu_assign_pointer(priv->lecd, NULL); + synchronize_rcu(); /* Do something needful? */ netif_stop_queue(dev); lec_arp_destroy(priv); - if (skb_peek(&sk_atm(vcc)->sk_receive_queue)) - pr_info("%s closing with messages pending\n", dev->name); - while ((skb = skb_dequeue(&sk_atm(vcc)->sk_receive_queue))) { - atm_return(vcc, skb->truesize); - dev_kfree_skb(skb); - } - pr_info("%s: Shut down!\n", dev->name); module_put(THIS_MODULE); } @@ -510,12 +521,14 @@ send_to_lecd(struct lec_priv *priv, atmlec_msg_type type, const unsigned char *mac_addr, const unsigned char *atm_addr, struct sk_buff *data) { + struct atm_vcc *vcc; struct sock *sk; struct sk_buff *skb; struct atmlec_msg *mesg; - if (!priv || !priv->lecd) + if (!priv || !rcu_access_pointer(priv->lecd)) return -1; + skb = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC); if (!skb) return -1; @@ -532,18 +545,27 @@ send_to_lecd(struct lec_priv *priv, atmlec_msg_type type, if (atm_addr) memcpy(&mesg->content.normal.atm_addr, atm_addr, ATM_ESA_LEN); - atm_force_charge(priv->lecd, skb->truesize); - sk = sk_atm(priv->lecd); + rcu_read_lock(); + vcc = rcu_dereference(priv->lecd); + if (!vcc) { + rcu_read_unlock(); + kfree_skb(skb); + return -1; + } + + atm_force_charge(vcc, skb->truesize); + sk = sk_atm(vcc); skb_queue_tail(&sk->sk_receive_queue, skb); sk->sk_data_ready(sk); if (data != NULL) { pr_debug("about to send %d bytes of data\n", data->len); - atm_force_charge(priv->lecd, data->truesize); + atm_force_charge(vcc, data->truesize); skb_queue_tail(&sk->sk_receive_queue, data); sk->sk_data_ready(sk); } + rcu_read_unlock(); return 0; } @@ -618,7 +640,7 @@ static void lec_push(struct atm_vcc *vcc, struct sk_buff *skb) atm_return(vcc, skb->truesize); if (*(__be16 *) skb->data == htons(priv->lecid) || - !priv->lecd || !(dev->flags & IFF_UP)) { + !rcu_access_pointer(priv->lecd) || !(dev->flags & IFF_UP)) { /* * Probably looping back, or if lecd is missing, * lecd has gone down @@ -753,12 +775,12 @@ static int lecd_attach(struct atm_vcc *vcc, int arg) priv = netdev_priv(dev_lec[i]); } else { priv = netdev_priv(dev_lec[i]); - if (priv->lecd) + if (rcu_access_pointer(priv->lecd)) return -EADDRINUSE; } lec_arp_init(priv); priv->itfnum = i; /* LANE2 addition */ - priv->lecd = vcc; + rcu_assign_pointer(priv->lecd, vcc); vcc->dev = &lecatm_dev; vcc_insert_socket(sk_atm(vcc)); diff --git a/net/atm/lec.h b/net/atm/lec.h index be0e2667bd8c3..ec85709bf8185 100644 --- a/net/atm/lec.h +++ b/net/atm/lec.h @@ -91,7 +91,7 @@ struct lec_priv { */ spinlock_t lec_arp_lock; struct atm_vcc *mcast_vcc; /* Default Multicast Send VCC */ - struct atm_vcc *lecd; + struct atm_vcc __rcu *lecd; struct delayed_work lec_arp_work; /* C10 */ unsigned int maximum_unknown_frame_count; /* -- 2.51.0