CVE-2026-46227
Description
In the Linux kernel, the following vulnerability has been resolved:
sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
The SCTP_SENDALL path in sctp_sendmsg() iterates ep->asocs with list_for_each_entry_safe(), which caches the next entry in @tmp before the loop body runs. The body calls sctp_sendmsg_to_asoc(), which may drop the socket lock inside sctp_wait_for_sndbuf().
While the lock is dropped, another thread can SCTP_SOCKOPT_PEELOFF the association cached in @tmp, migrating it to a new endpoint via sctp_sock_migrate() (list_del_init() + list_add_tail() to newep->asocs), and optionally close the new socket which frees the association via kfree_rcu(). The cached @tmp can also be freed by a network ABORT for that association, processed in softirq while the lock is dropped.
sctp_wait_for_sndbuf() revalidates @asoc (the current entry) on re-lock via the "sk != asoc->base.sk" and "asoc->base.dead" checks, but nothing revalidates @tmp. After a successful return, the iterator advances to the stale @tmp, yielding either a use-after-free (if the peeled socket was closed) or a list-walk onto the new endpoint's list head (type confusion of &newep->asocs as a struct sctp_association *).
Both are reachable from CapEff=0; the type-confusion path gives controlled indirect call via the outqueue.sched->init_sid pointer.
Fix by re-deriving @tmp from @asoc after sctp_sendmsg_to_asoc() returns. @asoc is known to still be on ep->asocs at that point: the only callers that list_del an association from ep->asocs are sctp_association_free() (which sets asoc->base.dead) and sctp_assoc_migrate() (which changes asoc->base.sk), and sctp_wait_for_sndbuf() checks both under the lock before any successful return; a tripped check propagates as err < 0 and the loop bails before the re-derive.
The SCTP_ABORT path in sctp_sendmsg_check_sflags() returns 0 and the loop hits 'continue' before sctp_sendmsg_to_asoc() is ever called, so the @tmp cached by list_for_each_entry_safe() still covers the lock-held free that ba59fb027307 ("sctp: walk the list of asoc safely") was added for.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Linux kernel SCTP SENDALL path has a use-after-free due to stale list cursor after dropping socket lock, allowing privilege escalation.
Vulnerability
In the Linux kernel's SCTP implementation, the SCTP_SENDALL path in sctp_sendmsg() iterates over the endpoint's association list (ep->asocs) using list_for_each_entry_safe(), which caches the next entry in @tmp. The loop body calls sctp_sendmsg_to_asoc(), which may drop the socket lock inside sctp_wait_for_sndbuf(). While the lock is dropped, another thread can peel off the association cached in @tmp via SCTP_SOCKOPT_PEELOFF, migrating it to a new endpoint or freeing it. The cached @tmp is not revalidated after re-acquiring the lock, leading to a use-after-free or type confusion. This affects all Linux kernel versions with SCTP support prior to the fix commit 1bfb06ecb00f [1].
Exploitation
An attacker needs local access with CapEff=0 (i.e., unprivileged) and the ability to create SCTP sockets and trigger the SCTP_SENDALL sendmsg path. The attacker must also be able to concurrently perform a SCTP_SOCKOPT_PEELOFF on an association that is being iterated, or cause a network ABORT for that association. The race window occurs when sctp_wait_for_sndbuf() drops the socket lock; another thread can then migrate or free the cached next association. After the lock is re-acquired, the iterator advances to the stale pointer.
Impact
Successful exploitation can lead to a use-after-free (if the peeled socket is closed and the association freed) or a type confusion (if the association is migrated to a new endpoint's list, causing the iterator to treat the new endpoint's list head as an sctp_association structure). The type-confusion path can give a controlled indirect call via the outqueue.sched->init_sid pointer, potentially allowing arbitrary code execution with kernel privileges. This results in full compromise of confidentiality, integrity, and availability.
Mitigation
The fix is included in the Linux kernel commit 1bfb06ecb00f7fdf35dba8e8f2877346cbe5e078 [1]. Users should apply the patch or update to a kernel version containing this commit. No workaround is available; the vulnerability requires a kernel update. The CVE is not listed on CISA's Known Exploited Vulnerabilities (KEV) catalog as of publication.
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2- Range: < c9dadb31f36045a8cb65df4bd75e7237ef21a4b5 (v6.14-rc3)
Patches
10c9dadb31f360sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index d190e75e46454a..c57a53192beef1 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1985,6 +1985,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
abb5f36771ccsctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index 58d0d9747f0b39..1d2568bb6bc277 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1986,6 +1986,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
1bfb06ecb00fsctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index 852c4f66eab5d7..b3c19210667fbb 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1985,6 +1985,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
6187a172d6edsctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index b6956b25b33d33..c8038b4b67c710 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1986,6 +1986,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
bf0f40d8107esctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index 05fb00c9c33576..48759da0a0261a 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1986,6 +1986,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
abb5f36771ccsctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index 58d0d9747f0b39..1d2568bb6bc277 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1986,6 +1986,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
1bfb06ecb00fsctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
2 files changed · +18 −2
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index 852c4f66eab5d7..b3c19210667fbb 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1985,6 +1985,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index 852c4f66eab5d7..b3c19210667fbb 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1985,6 +1985,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
6187a172d6edsctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index b6956b25b33d33..c8038b4b67c710 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1986,6 +1986,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
bf0f40d8107esctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index 05fb00c9c33576..48759da0a0261a 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1986,6 +1986,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
c9dadb31f360sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL
1 file changed · +9 −1
net/sctp/socket.c+9 −1 modifieddiff --git a/net/sctp/socket.c b/net/sctp/socket.c index d190e75e46454a..c57a53192beef1 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1985,6 +1985,15 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len) goto out_unlock; iov_iter_revert(&msg->msg_iter, err); + + /* sctp_sendmsg_to_asoc() may have released the socket + * lock (sctp_wait_for_sndbuf), during which other + * associations on ep->asocs could have been peeled + * off or freed. @asoc itself is revalidated by the + * base.dead and base.sk checks in sctp_wait_for_sndbuf, + * so re-derive the cached cursor from it. + */ + tmp = list_next_entry(asoc, asocs); } goto out_unlock; -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Stale list cursor in SCTP_SENDALL loop: after sctp_sendmsg_to_asoc() drops the socket lock, the cached @tmp entry may have been peeled off or freed, but the iterator is never revalidated."
Attack vector
An attacker with a local unprivileged socket (CapEff=0) can trigger the SCTP_SENDALL path in sctp_sendmsg() [patch_id=2897602]. The loop uses list_for_each_entry_safe() which caches the next association in @tmp. Inside the loop, sctp_sendmsg_to_asoc() may drop the socket lock via sctp_wait_for_sndbuf(). While the lock is dropped, a concurrent thread can call SCTP_SOCKOPT_PEELOFF to migrate the association cached in @tmp to a new endpoint, or a network ABORT can free it via kfree_rcu(). When the lock is re-acquired, the iterator advances to the stale @tmp, causing either a use-after-free or type confusion (walking onto the new endpoint's list head as if it were an sctp_association). The type-confusion path yields a controlled indirect call through outqueue.sched->init_sid.
Affected code
The vulnerable code is in `net/sctp/socket.c` in the `sctp_sendmsg()` function, specifically the SCTP_SENDALL path that iterates `ep->asocs` with `list_for_each_entry_safe()` [patch_id=2897602]. The loop body calls `sctp_sendmsg_to_asoc()`, which can drop the socket lock inside `sctp_wait_for_sndbuf()`.
What the fix does
The patch adds a single line after sctp_sendmsg_to_asoc() returns: `tmp = list_next_entry(asoc, asocs);` [patch_id=2897602]. This re-derives the cached cursor from @asoc, which is known to still be valid on ep->asocs because sctp_wait_for_sndbuf() revalidates it via the base.dead and base.sk checks before any successful return. If those checks fail, err < 0 causes the loop to bail before reaching the re-derive. This closes both the use-after-free and type-confusion paths without changing the lock semantics.
Preconditions
- authAttacker needs a local unprivileged socket (CapEff=0) capable of SCTP communication.
- configThe SCTP_SENDALL flag must be used in sctp_sendmsg(), and at least one association must exist on ep->asocs.
- networkA concurrent thread must perform SCTP_SOCKOPT_PEELOFF on the association cached in @tmp, or a network ABORT for that association must arrive in softirq while the socket lock is dropped.
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/1bfb06ecb00f7fdf35dba8e8f2877346cbe5e078nvd
- git.kernel.org/stable/c/6187a172d6ed57d6b2c327836e4407c6456e639dnvd
- git.kernel.org/stable/c/abb5f36771cc4c05899b34000829a787572a8817nvd
- git.kernel.org/stable/c/bf0f40d8107e2ce827521968dc6926f3e13728aenvd
- git.kernel.org/stable/c/c9dadb31f36045a8cb65df4bd75e7237ef21a4b5nvd
News mentions
0No linked articles in our index yet.