kernel/net: free TCP sockets closed before the handshake completes #620

Merged
Leency merged 2 commits from kernel-tcp-close-unsynchronized into main 2026-08-03 18:08:32 +00:00
Owner

Summary: socket_close() skipped both tcp_disconnect and socket_free for any
TCP socket that was not yet in the SS_ISCONNECTED state, leaking the socket
structure and leaving it on the net_sockets list forever.

Details:
The SS_ISCONNECTED gate in socket_close covered only fully established
connections. A socket closed while in SYN_SENT, SYN_RECEIVED or LISTEN, or
one that never connected at all, fell through to a bare ret: it was
neither disconnected nor freed. The 4 KiB socket structure and its two ring
buffers stayed allocated, the socket kept its place on net_sockets, its
timers kept being decremented by tcp_timer_640ms, and SOCKET.TID kept
pointing at a thread that was about to exit. A browser or any other
application that opens connections which fail to establish (refused,
filtered, or simply cancelled by the user) leaked one socket per attempt.

The gate is not needed: tcp_disconnect dispatches on the TCB state itself
and jumps straight to tcp_close -- which calls socket_free -- whenever
t_state is below TCPS_ESTABLISHED. Dropping the test therefore routes the
not-yet-synchronized cases to exactly the cleanup they were missing, and
leaves the established path untouched. The SS_ISDISCONNECTING test is kept,
so a second close() on a socket already shutting down is still a no-op.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

Summary: socket_close() skipped both tcp_disconnect and socket_free for any TCP socket that was not yet in the SS_ISCONNECTED state, leaking the socket structure and leaving it on the net_sockets list forever. Details: The SS_ISCONNECTED gate in socket_close covered only fully established connections. A socket closed while in SYN_SENT, SYN_RECEIVED or LISTEN, or one that never connected at all, fell through to a bare `ret`: it was neither disconnected nor freed. The 4 KiB socket structure and its two ring buffers stayed allocated, the socket kept its place on net_sockets, its timers kept being decremented by tcp_timer_640ms, and SOCKET.TID kept pointing at a thread that was about to exit. A browser or any other application that opens connections which fail to establish (refused, filtered, or simply cancelled by the user) leaked one socket per attempt. The gate is not needed: tcp_disconnect dispatches on the TCB state itself and jumps straight to tcp_close -- which calls socket_free -- whenever t_state is below TCPS_ESTABLISHED. Dropping the test therefore routes the not-yet-synchronized cases to exactly the cleanup they were missing, and leaves the established path untouched. The SS_ISDISCONNECTING test is kept, so a second close() on a socket already shutting down is still a no-op. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
hidnplayr requested changes 2026-07-30 13:32:21 +00:00
Dismissed
hidnplayr left a comment
Owner

Agreed, but no need for all this blabla in source :)

Agreed, but no need for all this blabla in source :)
hidnplayr self-assigned this 2026-08-01 08:57:14 +00:00
Author
Owner

Agreed, but no need for all this blabla in source :)

Cleared.

> Agreed, but no need for all this blabla in source :) Cleared.
hidnplayr approved these changes 2026-08-02 20:35:55 +00:00
Leency requested review from Doczom 2026-08-02 20:48:37 +00:00
Doczom approved these changes 2026-08-03 01:48:45 +00:00
Leency added 2 commits 2026-08-03 18:06:15 +00:00
Summary: socket_close() skipped both tcp_disconnect and socket_free for any
TCP socket that was not yet in the SS_ISCONNECTED state, leaking the socket
structure and leaving it on the net_sockets list forever.

Подробно:
The SS_ISCONNECTED gate in socket_close covered only fully established
connections. A socket closed while in SYN_SENT, SYN_RECEIVED or LISTEN, or
one that never connected at all, fell through to a bare `ret`: it was
neither disconnected nor freed. The 4 KiB socket structure and its two ring
buffers stayed allocated, the socket kept its place on net_sockets, its
timers kept being decremented by tcp_timer_640ms, and SOCKET.TID kept
pointing at a thread that was about to exit. A browser or any other
application that opens connections which fail to establish (refused,
filtered, or simply cancelled by the user) leaked one socket per attempt.

The gate is not needed: tcp_disconnect dispatches on the TCB state itself
and jumps straight to tcp_close -- which calls socket_free -- whenever
t_state is below TCPS_ESTABLISHED. Dropping the test therefore routes the
not-yet-synchronized cases to exactly the cleanup they were missing, and
leaves the established path untouched. The SS_ISDISCONNECTING test is kept,
so a second close() on a socket already shutting down is still a no-op.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
remove comments
Check kernel codestyle / Check kernel codestyle (pull_request) Successful in 21s
Test PR / Build (en_US) (pull_request) Successful in 1m48s
Test PR / Build (ru_RU) (pull_request) Successful in 2m1s
Test PR / Build (es_ES) (pull_request) Successful in 2m6s
062e7fae2b
Leency force-pushed kernel-tcp-close-unsynchronized from 1fd37a67cb to 062e7fae2b 2026-08-03 18:06:15 +00:00 Compare
Leency scheduled this pull request to auto merge when all checks succeed 2026-08-03 18:06:41 +00:00
Leency merged commit 8766bfc546 into main 2026-08-03 18:08:32 +00:00
Leency deleted branch kernel-tcp-close-unsynchronized 2026-08-03 18:08:33 +00:00
Sign in to join this conversation.
No Reviewers
No labels
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: KolibriOS/kolibrios#620