Browse Source

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).
С фиксом тест работает без крашей.
chatgui
Evgeny 3 months ago
parent
commit
a97a38d867
  1. 3
      lib/tcp_io.c
  2. 1242
      lib/u_async.c
  3. 8
      tests/test_uasync_socket_race.c

3
lib/tcp_io.c

@ -158,7 +158,6 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
}
if (tc->sock != SOCKET_INVALID) {
// Гарантированно удаляем из epoll (uasync_remove_socket_t может пропустить DEL если нода уже inactive)
uasync_remove_socket_t(tc->ua, tc->sock);
tc->socket_id = NULL;
#if HAS_EPOLL
@ -166,7 +165,7 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
epoll_ctl(tc->ua->epoll_fd, EPOLL_CTL_DEL, (int)tc->sock, NULL);
#endif
socket_close_wrapper(tc->sock);
tc->sock = SOCKET_INVALID;
tc->sock = SOCKET_INVALID; // ДО on_error: read/write в том же epoll event увидят INVALID
}
queue_set_callback(tc->write_queue, NULL, NULL);

1242
lib/u_async.c

File diff suppressed because it is too large Load Diff

8
tests/test_uasync_socket_race.c

@ -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();

Loading…
Cancel
Save