VYPR
Unrated severityNVD Advisory· Published May 28, 2026

CVE-2026-46227

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

Patches

10
c9dadb31f360

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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
    
    
    
abb5f36771cc

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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
    
    
    
1bfb06ecb00f

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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
    
    
    
6187a172d6ed

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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
    
    
    
bf0f40d8107e

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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
    
    
    
abb5f36771cc

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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
    
    
    
1bfb06ecb00f

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

2 files changed · +18 2
  • net/sctp/socket.c+9 1 modified
    diff --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 modified
    diff --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
    
    
    
6187a172d6ed

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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
    
    
    
bf0f40d8107e

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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
    
    
    
c9dadb31f360

sctp: revalidate list cursor after sctp_sendmsg_to_asoc() in SCTP_SENDALL

1 file changed · +9 1
  • net/sctp/socket.c+9 1 modified
    diff --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

News mentions

0

No linked articles in our index yet.