fix: set tc->sock=SOCKET_INVALID before on_error in handle_error

В одном epoll_wait batch три события для fd: EPOLLERR (освобождает tc),
EPOLLIN (read_cb), EPOLLOUT (write_cb). read_cb/write_cb guard проверяет
tc->sock==SOCKET_INVALID, но sock обнулялся ПОСЛЕ on_error.
Перенос sock=SOCKET_INVALID до on_error защищает от use-after-free.

Доказано: без фикса segfault на tcp_io.c:374 (write_cb → tc->connected).
С фиксом тест работает без крашей.
This commit is contained in:
Evgeny committed 2026-07-02 00:43:37 +03:00
1 parent aff3b2d00f
commit a97a38d867
3 files changed
+627 -622

No files matched your search

+5 -3
View File
@@ -1,5 +1,6 @@
// test_uasync_socket_race.c — тест гонки fd-reuse в epoll при быстром accept/close/accept
// 4 дочерних процесса, каждый 200 connect+send+close. Сервер на uasync+tcp_io.
// Проверяет что нет use-after-free при двойном epoll-событии (ERROR→free tc, WRITE→stale).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -104,9 +105,11 @@ static void test_timeout(void* arg) {
int main(void) {
printf("=== test_uasync_socket_race ===\n"); fflush(stdout);
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
debug_config_init();
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) debug_set_category_level(i, DEBUG_LEVEL_ERROR);
srand((unsigned)getpid());
close(0); open("/dev/null", O_RDONLY);
close(0); open("/dev/null", O_RDONLY); // резервируем fd 0
g_port = 25000 + (rand() % 10000);
g_ua = uasync_create();
@@ -122,7 +125,6 @@ int main(void) {
if (bind(lsock, (struct sockaddr*)&la, sizeof(la)) < 0) { printf("[FAIL] bind: %s\n", strerror(errno)); goto cleanup; }
if (listen(lsock, 32) < 0) { printf("[FAIL] listen: %s\n", strerror(errno)); goto cleanup; }
uasync_add_socket(g_ua, lsock, on_accept_cb, NULL, NULL, NULL);
printf(" listen fd=%d epoll ok\n", lsock); fflush(stdout);
for (int i = 0; i < CHILDREN; i++) {
pid_t pid = fork();