* [PATCH net v2] sctp: revalidate output stream after association connect wait
@ 2026-10-07 3:57 Daehyeon Ko
2026-10-08 15:57 ` netdev-bot+sashiko
2026-10-09 1:12 ` Xin Long
0 siblings, 2 replies; 4+ messages in thread
From: Daehyeon Ko @ 2026-10-07 3:57 UTC (permalink / raw)
To: Marcelo Ricardo Leitner, Xin Long
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, linux-sctp, netdev, linux-kernel, Daehyeon Ko,
stable
When message interleaving is enabled, the first send waits for association
establishment before building its data chunks. The wait drops the socket
lock, and handshake processing can reduce the output stream count to the
peer-advertised inbound stream count. sctp_stream_init() then frees the
extension of every removed stream.
The sender resumes with the stream that it checked before the wait. If the
peer removed that stream, sctp_outq_tail() later dereferences its NULL
extension. Fatal-oops policies then panic the host.
KASAN: null-ptr-deref in range [0x38-0x3f]
RIP: sctp_outq_tail+0x49e/0xaa0
Call Trace:
sctp_primitive_SEND
sctp_sendmsg_to_asoc
sctp_sendmsg
A range check alone is insufficient. Stream reconfiguration can grow the
output count again while the lock is released without recreating extensions
freed by the earlier shrink. The stream can then be in range while its
extension remains NULL. The send-buffer wait has the same issue.
Factor the range and extension checks into a helper and repeat both after
each send-path wait that drops the socket lock. Once the lock is
reacquired, the validation remains stable through data creation and
queueing.
If validation fails after the connect wait, the auto-created association is
already established. Return ESRCH so the existing caller path keeps it
under state-machine ownership instead of freeing it directly, which would
leave protocol, counter and socket state inconsistent.
Fixes: 668c9beb9020 ("sctp: implement assign_number for sctp_stream_interleave")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Daehyeon Ko <4ncienth@gmail.com>
---
Changes in v2:
- Repeat full stream validation after both send-path waits.
- Recreate a missing stream extension after shrink followed by growth.
- Return ESRCH after the connect wait to retain the live association.
v1: https://lore.kernel.org/r/20261002010449.3689454-1-4ncienth@gmail.com
net/sctp/socket.c | 33 +++++++++++++++++++++------------
1 file changed, 21 insertions(+), 12 deletions(-)
diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index 4652fd90d9a6c4..aaeb58ce055771 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -1786,6 +1786,18 @@ static int sctp_sendmsg_check_sflags(struct sctp_association *asoc,
return 1;
}
+static int sctp_sendmsg_check_stream(struct sctp_association *asoc,
+ struct sctp_sndrcvinfo *sinfo)
+{
+ if (unlikely(sinfo->sinfo_stream >= asoc->stream.outcnt))
+ return -EINVAL;
+
+ if (unlikely(!SCTP_SO(&asoc->stream, sinfo->sinfo_stream)->ext))
+ return sctp_stream_init_ext(&asoc->stream, sinfo->sinfo_stream);
+
+ return 0;
+}
+
static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
struct msghdr *msg, size_t msg_len,
struct sctp_transport *transport,
@@ -1800,16 +1812,9 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
long timeo;
int err;
- if (sinfo->sinfo_stream >= asoc->stream.outcnt) {
- err = -EINVAL;
+ err = sctp_sendmsg_check_stream(asoc, sinfo);
+ if (err)
goto err;
- }
-
- if (unlikely(!SCTP_SO(&asoc->stream, sinfo->sinfo_stream)->ext)) {
- err = sctp_stream_init_ext(&asoc->stream, sinfo->sinfo_stream);
- if (err)
- goto err;
- }
if (sp->disable_fragments && msg_len > asoc->frag_point) {
err = -EMSGSIZE;
@@ -1830,10 +1835,9 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
err = sctp_wait_for_sndbuf(asoc, transport, &timeo, msg_len);
if (err)
goto err;
- if (unlikely(sinfo->sinfo_stream >= asoc->stream.outcnt)) {
- err = -EINVAL;
+ err = sctp_sendmsg_check_stream(asoc, sinfo);
+ if (err)
goto err;
- }
}
if (sctp_state(asoc, CLOSED)) {
@@ -1848,6 +1852,11 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
err = -ESRCH;
goto err;
}
+ err = sctp_sendmsg_check_stream(asoc, sinfo);
+ if (err) {
+ err = -ESRCH;
+ goto err;
+ }
} else {
wait_connect = true;
}
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net v2] sctp: revalidate output stream after association connect wait
2026-10-07 3:57 [PATCH net v2] sctp: revalidate output stream after association connect wait Daehyeon Ko
@ 2026-10-08 15:57 ` netdev-bot+sashiko
2026-10-09 1:10 ` Xin Long
2026-10-09 1:12 ` Xin Long
1 sibling, 1 reply; 4+ messages in thread
From: netdev-bot+sashiko @ 2026-10-08 15:57 UTC (permalink / raw)
To: 4ncienth
Cc: marcelo.leitner, lucien.xin, davem, edumazet, kuba, pabeni,
horms, linux-sctp, netdev, linux-kernel, stable
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] sctp: UAPI breakage leaking -ESRCH to userspace on stream
validation failure
Pre-existing issues:
- [Medium] Pre-existing ownership problem on the sndbuf-wait error path
in net/sctp/socket.c, not introduced by this patch.
--
sctp: revalidate output stream after association connect wait
This commit moves the output stream range check and the extension check
into a new helper, sctp_sendmsg_check_stream(). It runs that helper again
after both the sndbuf wait and the connect wait in sctp_sendmsg_to_asoc().
If the check fails after the connect wait, the error becomes -ESRCH, so the
caller does not free the auto-created, already established association.
> diff --git a/net/sctp/socket.c b/net/sctp/socket.c
> index e35692dd8d303..321898f22d80d 100644
> --- a/net/sctp/socket.c
> +++ b/net/sctp/socket.c
> @@ -1786,6 +1786,18 @@ static int sctp_sendmsg_check_sflags(struct sctp_association *asoc,
> return 1;
> }
>
> +static int sctp_sendmsg_check_stream(struct sctp_association *asoc,
> + struct sctp_sndrcvinfo *sinfo)
> +{
> + if (unlikely(sinfo->sinfo_stream >= asoc->stream.outcnt))
> + return -EINVAL;
> +
> + if (unlikely(!SCTP_SO(&asoc->stream, sinfo->sinfo_stream)->ext))
> + return sctp_stream_init_ext(&asoc->stream, sinfo->sinfo_stream);
> +
> + return 0;
> +}
> +
[ ... ]
> @@ -1830,10 +1835,9 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
> err = sctp_wait_for_sndbuf(asoc, transport, &timeo, msg_len);
> if (err)
> goto err;
> - if (unlikely(sinfo->sinfo_stream >= asoc->stream.outcnt)) {
> - err = -EINVAL;
> + err = sctp_sendmsg_check_stream(asoc, sinfo);
> + if (err)
> goto err;
> - }
> }
[Severity: Medium]
This is a pre-existing issue, not one introduced by this patch. Does the
ownership reasoning from the connect wait also apply to this re-check after
the sndbuf wait?
The caller, sctp_sendmsg(), frees a new association on any error except
-ESRCH. It does not check whether the association is still CLOSED:
err = sctp_sendmsg_to_asoc(asoc, msg, msg_len, transport, sinfo);
if (err < 0 && err != -ESRCH && new)
sctp_association_free(asoc);
sctp_assoc_add_peer() has already hashed the new association's transport at
this point. On a UDP-style socket, another sender can find the association
through sctp_endpoint_lookup_assoc() while this thread sleeps in
sctp_wait_for_sndbuf() with the socket lock dropped.
A possible sequence:
thread A (creates the asoc, new == true)
sctp_sendmsg_to_asoc()
sctp_wait_for_sndbuf()
release_sock()
thread B (sends to the same peer)
sctp_sendmsg()
sctp_endpoint_lookup_assoc() <- finds the CLOSED asoc
sctp_sendmsg_to_asoc()
sctp_primitive_ASSOCIATE()
the peer's INIT-ACK shrinks asoc->stream.outcnt below A's sinfo_stream
thread A
lock_sock()
sctp_sendmsg_check_stream() <- returns -EINVAL
sctp_sendmsg()
sctp_association_free()
Would this tear down an association that the state machine now owns
(COOKIE_ECHOED or ESTABLISHED)? No ABORT would be sent to the peer, and the
counter and socket state would be left inconsistent. The commit message says
it avoids this outcome for the connect wait by returning -ESRCH.
The baseline already had the -EINVAL re-check here. The other
sctp_wait_for_sndbuf() errors (-EAGAIN, -EPIPE, -ERESTARTSYS) also reach
the same free. With this patch, sctp_stream_init_ext() failures (-ENOMEM or
a scheduler init_sid error) reach it as well.
Should sctp_sendmsg() check whether the association is still CLOSED before
freeing it? Or should this path remap the error the same way the connect
wait path does? Earlier commits c863850ce22e1b and 4f1da630d13de0 used a
similar -ESRCH remapping.
[ ... ]
> @@ -1848,6 +1852,11 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
> err = -ESRCH;
> goto err;
> }
> + err = sctp_sendmsg_check_stream(asoc, sinfo);
> + if (err) {
> + err = -ESRCH;
> + goto err;
> + }
> } else {
> wait_connect = true;
> }
[Severity: Medium]
Does this leak the internal -ESRCH sentinel to userspace?
sctp_sendmsg() returns its result through sctp_error(), and
sctp_error() only special-cases -EPIPE. Whatever
sctp_sendmsg_to_asoc() returns here therefore reaches the sendmsg()
caller unchanged.
With this hunk, sending on an sinfo_stream that the peer did not grant
can return ESRCH. That happens only on the first send of an implicitly
created association with intl_enable set. The same request on an
existing association, or after the sndbuf wait, returns EINVAL. An
-ENOMEM from sctp_stream_init_ext(), or an error from the scheduler's
init_sid hook, is also reported as ESRCH.
ESRCH ("No such process") hides the real cause from the application.
It also misleads, because the association is alive and ESTABLISHED at
this point, and a later send on a valid stream would succeed. Since
this is Cc stable, the errno change would also appear in stable
kernels.
The connect wait failure path just above already returns -ESRCH to
userspace, but that case is pre-existing. This patch extends the
behavior to an argument validation failure.
The remapping only exists to keep sctp_sendmsg() from calling
sctp_association_free(). Could the ownership decision be made
separately from the errno? For example, sctp_sendmsg() could free the
new association only while it is still CLOSED. Alternatively,
sctp_sendmsg_to_asoc() could report ownership through a separate
out-parameter. Either way the original -EINVAL or -ENOMEM would reach
userspace. Or is there a reason the errno has to carry this?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261007035739.3472432-1-4ncienth%40gmail.com
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net v2] sctp: revalidate output stream after association connect wait
2026-10-08 15:57 ` netdev-bot+sashiko
@ 2026-10-09 1:10 ` Xin Long
0 siblings, 0 replies; 4+ messages in thread
From: Xin Long @ 2026-10-09 1:10 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: 4ncienth, marcelo.leitner, davem, edumazet, kuba, pabeni, horms,
linux-sctp, netdev, linux-kernel, stable
On Thu, Oct 8, 2026 at 11:57 AM <netdev-bot+sashiko@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider.
>
> Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [Medium] sctp: UAPI breakage leaking -ESRCH to userspace on stream
> validation failure
>
> Pre-existing issues:
> - [Medium] Pre-existing ownership problem on the sndbuf-wait error path
> in net/sctp/socket.c, not introduced by this patch.
>
> --
>
> sctp: revalidate output stream after association connect wait
>
> This commit moves the output stream range check and the extension check
> into a new helper, sctp_sendmsg_check_stream(). It runs that helper again
> after both the sndbuf wait and the connect wait in sctp_sendmsg_to_asoc().
> If the check fails after the connect wait, the error becomes -ESRCH, so the
> caller does not free the auto-created, already established association.
>
> > diff --git a/net/sctp/socket.c b/net/sctp/socket.c
> > index e35692dd8d303..321898f22d80d 100644
> > --- a/net/sctp/socket.c
> > +++ b/net/sctp/socket.c
> > @@ -1786,6 +1786,18 @@ static int sctp_sendmsg_check_sflags(struct sctp_association *asoc,
> > return 1;
> > }
> >
> > +static int sctp_sendmsg_check_stream(struct sctp_association *asoc,
> > + struct sctp_sndrcvinfo *sinfo)
> > +{
> > + if (unlikely(sinfo->sinfo_stream >= asoc->stream.outcnt))
> > + return -EINVAL;
> > +
> > + if (unlikely(!SCTP_SO(&asoc->stream, sinfo->sinfo_stream)->ext))
> > + return sctp_stream_init_ext(&asoc->stream, sinfo->sinfo_stream);
> > +
> > + return 0;
> > +}
> > +
>
> [ ... ]
>
> > @@ -1830,10 +1835,9 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
> > err = sctp_wait_for_sndbuf(asoc, transport, &timeo, msg_len);
> > if (err)
> > goto err;
> > - if (unlikely(sinfo->sinfo_stream >= asoc->stream.outcnt)) {
> > - err = -EINVAL;
> > + err = sctp_sendmsg_check_stream(asoc, sinfo);
> > + if (err)
> > goto err;
> > - }
> > }
>
> [Severity: Medium]
> This is a pre-existing issue, not one introduced by this patch. Does the
> ownership reasoning from the connect wait also apply to this re-check after
> the sndbuf wait?
>
> The caller, sctp_sendmsg(), frees a new association on any error except
> -ESRCH. It does not check whether the association is still CLOSED:
>
> err = sctp_sendmsg_to_asoc(asoc, msg, msg_len, transport, sinfo);
> if (err < 0 && err != -ESRCH && new)
> sctp_association_free(asoc);
>
> sctp_assoc_add_peer() has already hashed the new association's transport at
> this point. On a UDP-style socket, another sender can find the association
> through sctp_endpoint_lookup_assoc() while this thread sleeps in
> sctp_wait_for_sndbuf() with the socket lock dropped.
>
> A possible sequence:
>
> thread A (creates the asoc, new == true)
> sctp_sendmsg_to_asoc()
> sctp_wait_for_sndbuf()
> release_sock()
>
I don't think sctp_wait_for_sndbuf() can be called for the first send
(new == true) because sctp_sendmsg_parse() already checks:
if (msg_len > sk->sk_sndbuf)
return -EMSGSIZE;
> thread B (sends to the same peer)
> sctp_sendmsg()
> sctp_endpoint_lookup_assoc() <- finds the CLOSED asoc
> sctp_sendmsg_to_asoc()
> sctp_primitive_ASSOCIATE()
>
> the peer's INIT-ACK shrinks asoc->stream.outcnt below A's sinfo_stream
>
> thread A
> lock_sock()
> sctp_sendmsg_check_stream() <- returns -EINVAL
> sctp_sendmsg()
> sctp_association_free()
>
> Would this tear down an association that the state machine now owns
> (COOKIE_ECHOED or ESTABLISHED)? No ABORT would be sent to the peer, and the
> counter and socket state would be left inconsistent. The commit message says
> it avoids this outcome for the connect wait by returning -ESRCH.
>
> The baseline already had the -EINVAL re-check here. The other
> sctp_wait_for_sndbuf() errors (-EAGAIN, -EPIPE, -ERESTARTSYS) also reach
> the same free. With this patch, sctp_stream_init_ext() failures (-ENOMEM or
> a scheduler init_sid error) reach it as well.
>
> Should sctp_sendmsg() check whether the association is still CLOSED before
> freeing it? Or should this path remap the error the same way the connect
> wait path does? Earlier commits c863850ce22e1b and 4f1da630d13de0 used a
> similar -ESRCH remapping.
>
> [ ... ]
>
> > @@ -1848,6 +1852,11 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
> > err = -ESRCH;
> > goto err;
> > }
> > + err = sctp_sendmsg_check_stream(asoc, sinfo);
> > + if (err) {
> > + err = -ESRCH;
> > + goto err;
> > + }
> > } else {
> > wait_connect = true;
> > }
>
> [Severity: Medium]
> Does this leak the internal -ESRCH sentinel to userspace?
>
> sctp_sendmsg() returns its result through sctp_error(), and
> sctp_error() only special-cases -EPIPE. Whatever
> sctp_sendmsg_to_asoc() returns here therefore reaches the sendmsg()
> caller unchanged.
>
> With this hunk, sending on an sinfo_stream that the peer did not grant
> can return ESRCH. That happens only on the first send of an implicitly
> created association with intl_enable set. The same request on an
> existing association, or after the sndbuf wait, returns EINVAL. An
> -ENOMEM from sctp_stream_init_ext(), or an error from the scheduler's
> init_sid hook, is also reported as ESRCH.
>
> ESRCH ("No such process") hides the real cause from the application.
> It also misleads, because the association is alive and ESTABLISHED at
> this point, and a later send on a valid stream would succeed. Since
> this is Cc stable, the errno change would also appear in stable
> kernels.
>
> The connect wait failure path just above already returns -ESRCH to
> userspace, but that case is pre-existing. This patch extends the
> behavior to an argument validation failure.
>
> The remapping only exists to keep sctp_sendmsg() from calling
> sctp_association_free(). Could the ownership decision be made
> separately from the errno? For example, sctp_sendmsg() could free the
> new association only while it is still CLOSED. Alternatively,
> sctp_sendmsg_to_asoc() could report ownership through a separate
> out-parameter. Either way the original -EINVAL or -ENOMEM would reach
> userspace. Or is there a reason the errno has to carry this?
>
This is not a typical user scenario, and this patch is sufficient to fix
the NULL pointer dereference.
I may address the original error returned to userspace in a separate patch
by passing &new to sctp_sendmsg_to_asoc() and setting it to false once the
association is established. This would also cover the failures reported in
v1, where sctp_datamsg_from_user() or sctp_primitive_SEND() may fail after
the wait and reach the same cleanup path.
Thanks.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net v2] sctp: revalidate output stream after association connect wait
2026-10-07 3:57 [PATCH net v2] sctp: revalidate output stream after association connect wait Daehyeon Ko
2026-10-08 15:57 ` netdev-bot+sashiko
@ 2026-10-09 1:12 ` Xin Long
1 sibling, 0 replies; 4+ messages in thread
From: Xin Long @ 2026-10-09 1:12 UTC (permalink / raw)
To: Daehyeon Ko
Cc: Marcelo Ricardo Leitner, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-sctp, netdev,
linux-kernel, stable
On Tue, Oct 6, 2026 at 11:57 PM Daehyeon Ko <4ncienth@gmail.com> wrote:
>
> When message interleaving is enabled, the first send waits for association
> establishment before building its data chunks. The wait drops the socket
> lock, and handshake processing can reduce the output stream count to the
> peer-advertised inbound stream count. sctp_stream_init() then frees the
> extension of every removed stream.
>
> The sender resumes with the stream that it checked before the wait. If the
> peer removed that stream, sctp_outq_tail() later dereferences its NULL
> extension. Fatal-oops policies then panic the host.
>
> KASAN: null-ptr-deref in range [0x38-0x3f]
> RIP: sctp_outq_tail+0x49e/0xaa0
> Call Trace:
> sctp_primitive_SEND
> sctp_sendmsg_to_asoc
> sctp_sendmsg
>
> A range check alone is insufficient. Stream reconfiguration can grow the
> output count again while the lock is released without recreating extensions
> freed by the earlier shrink. The stream can then be in range while its
> extension remains NULL. The send-buffer wait has the same issue.
>
> Factor the range and extension checks into a helper and repeat both after
> each send-path wait that drops the socket lock. Once the lock is
> reacquired, the validation remains stable through data creation and
> queueing.
>
> If validation fails after the connect wait, the auto-created association is
> already established. Return ESRCH so the existing caller path keeps it
> under state-machine ownership instead of freeing it directly, which would
> leave protocol, counter and socket state inconsistent.
>
> Fixes: 668c9beb9020 ("sctp: implement assign_number for sctp_stream_interleave")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Daehyeon Ko <4ncienth@gmail.com>
Acked-by: Xin Long <lucien.xin@gmail.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-09 1:12 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-07 3:57 [PATCH net v2] sctp: revalidate output stream after association connect wait Daehyeon Ko
2026-10-08 15:57 ` netdev-bot+sashiko
2026-10-09 1:10 ` Xin Long
2026-10-09 1:12 ` Xin Long
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®