kernel/net: fix use-after-free on refused TCP connections #622

Open
Leency wants to merge 1 commits from kernel-tcp-connrefused-uaf into main
Owner

Summary: when a RST+ACK arrived in SYN_SENT state, tcp_input released the
socket mutex after tcp_drop had already freed the socket, writing into
kernel heap that no longer belonged to it.

Details:
The "connection refused" path called tcp_drop and then jumped to .drop.
tcp_drop ends in tcp_close in both of its branches, and tcp_close calls
socket_free, which returns the 4 KiB socket structure to the kernel heap.
.drop then executed

    lea     ecx, [ebx + SOCKET.mutex]
    call    mutex_unlock

on that freed block, corrupting whatever had been allocated there in the
meantime. Every refused connection -- a closed port, a service that is
down -- hit this, so the damage was easy to trigger from user space and
showed up later as unrelated page faults.

The fix follows the pattern the same file already uses in .unlock_and_close:
unlock the mutex while the socket is still alive, then call tcp_drop, then
leave through .drop_no_socket, which frees the packet buffer without
touching the socket. tcp_drop takes the socket in eax, so ebx is pushed and
popped into eax around the unlock.

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

Summary: when a RST+ACK arrived in SYN_SENT state, tcp_input released the socket mutex after tcp_drop had already freed the socket, writing into kernel heap that no longer belonged to it. Details: The "connection refused" path called tcp_drop and then jumped to .drop. tcp_drop ends in tcp_close in both of its branches, and tcp_close calls socket_free, which returns the 4 KiB socket structure to the kernel heap. .drop then executed lea ecx, [ebx + SOCKET.mutex] call mutex_unlock on that freed block, corrupting whatever had been allocated there in the meantime. Every refused connection -- a closed port, a service that is down -- hit this, so the damage was easy to trigger from user space and showed up later as unrelated page faults. The fix follows the pattern the same file already uses in .unlock_and_close: unlock the mutex while the socket is still alive, then call tcp_drop, then leave through .drop_no_socket, which frees the packet buffer without touching the socket. tcp_drop takes the socket in eax, so ebx is pushed and popped into eax around the unlock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Leency added 1 commit 2026-07-29 10:40:09 +00:00
kernel/net: fix use-after-free on refused TCP connections
Check kernel codestyle / Check kernel codestyle (pull_request) Successful in 35s
Test PR / Build (en_US) (pull_request) Successful in 4m20s
Test PR / Build (es_ES) (pull_request) Successful in 3m59s
Test PR / Build (ru_RU) (pull_request) Successful in 1m48s
9215edd426
Summary: when a RST+ACK arrived in SYN_SENT state, tcp_input released the
socket mutex after tcp_drop had already freed the socket, writing into
kernel heap that no longer belonged to it.

Подробно:
The "connection refused" path called tcp_drop and then jumped to .drop.
tcp_drop ends in tcp_close in both of its branches, and tcp_close calls
socket_free, which returns the 4 KiB socket structure to the kernel heap.
.drop then executed

        lea     ecx, [ebx + SOCKET.mutex]
        call    mutex_unlock

on that freed block, corrupting whatever had been allocated there in the
meantime. Every refused connection -- a closed port, a service that is
down -- hit this, so the damage was easy to trigger from user space and
showed up later as unrelated page faults.

The fix follows the pattern the same file already uses in .unlock_and_close:
unlock the mutex while the socket is still alive, then call tcp_drop, then
leave through .drop_no_socket, which frees the packet buffer without
touching the socket. tcp_drop takes the socket in eax, so ebx is pushed and
popped into eax around the unlock.

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

This PR is a nice example of #606 and #607 combined.

Let me think a bit how to tackle this problem as a whole.
The proposed solution might work in practice for most cases but is not structural in the bigger picture.

This PR is a nice example of #606 and #607 combined. Let me think a bit how to tackle this problem as a whole. The proposed solution might work in practice for most cases but is not structural in the bigger picture.
hidnplayr self-assigned this 2026-08-01 08:58:14 +00:00
All checks were successful
Check kernel codestyle / Check kernel codestyle (pull_request) Successful in 35s
Test PR / Build (en_US) (pull_request) Successful in 4m20s
Required
Details
Test PR / Build (es_ES) (pull_request) Successful in 3m59s
Required
Details
Test PR / Build (ru_RU) (pull_request) Successful in 1m48s
Required
Details
Checking for merge conflicts…
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin kernel-tcp-connrefused-uaf:kernel-tcp-connrefused-uaf
git checkout kernel-tcp-connrefused-uaf
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: KolibriOS/kolibrios#622