fix: use-after-free in pkt_normalizer + ll_queue waiter cancel + uasync gen counter for epoll fd-reuse
- pkt_normalizer: save recvpart->len before queue_data_put (may free synchronously) - ll_queue: save uasync_call_soon handle in queue_waiter_handle, cancel on free - uasync: generation counter in epoll data.u64 to skip stale events after fd reuse - uasync: keep fd_to_index mapping after socket_array_remove for inactive nodes - test: test_uasync_socket_race — 4 children × 200 iterations, currently flaky
This commit is contained in:
1 parent
a01e2f0899
commit
ccdbab95a8
6 files changed
+228
-59
No files matched your search
+55
-52
@@ -52,20 +52,22 @@ struct socket_node {
|
||||
socket_t_callback_t write_cbk_sock; // For SOCK type
|
||||
socket_callback_t except_cbk;
|
||||
void* user_data;
|
||||
int active; // 1 if socket is active, 0 if u_freed (for reuse)
|
||||
int active; // 1 if socket is active, 0 if freed (for reuse)
|
||||
int enable_read; // 1 if read monitoring is enabled
|
||||
int enable_write; // 1 if write monitoring is enabled
|
||||
uint16_t gen; // generation counter for epoll event validation
|
||||
};
|
||||
|
||||
// Array-based socket management for O(1) operations
|
||||
struct socket_array {
|
||||
struct socket_node* sockets; // Dynamic array of socket nodes
|
||||
int* fd_to_index; // FD to array index mapping
|
||||
int* index_to_fd; // Array index to FD mapping
|
||||
int* active_indices; // Array of indices of active sockets (for O(1) traversal)
|
||||
int capacity; // Total allocated capacity
|
||||
int count; // Number of active sockets
|
||||
int max_fd; // Maximum FD for bounds checking
|
||||
struct socket_array {
|
||||
struct socket_node* sockets; // Dynamic array of socket nodes
|
||||
int* fd_to_index; // FD to array index mapping
|
||||
int* index_to_fd; // Array index to FD mapping
|
||||
int* active_indices; // Array of indices of active sockets (for O(1) traversal)
|
||||
int capacity; // Total allocated capacity
|
||||
int count; // Number of active sockets
|
||||
int max_fd; // Maximum FD for bounds checking
|
||||
uint16_t gen_counter; // incrementing generation for epoll stale-event detection
|
||||
};
|
||||
|
||||
static struct socket_array* socket_array_create(int initial_capacity);
|
||||
@@ -80,8 +82,9 @@ static struct socket_node* socket_array_get(struct socket_array* sa, int fd);
|
||||
static struct socket_array* socket_array_create(int initial_capacity) {
|
||||
if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity
|
||||
|
||||
struct socket_array* sa = u_malloc(sizeof(struct socket_array));
|
||||
if (!sa) return NULL;
|
||||
struct socket_array* sa = u_malloc(sizeof(struct socket_array));
|
||||
if (!sa) return NULL;
|
||||
sa->gen_counter = 0;
|
||||
|
||||
sa->sockets = u_calloc(initial_capacity, sizeof(struct socket_node));
|
||||
sa->fd_to_index = u_calloc(initial_capacity, sizeof(int));
|
||||
@@ -167,20 +170,22 @@ static int socket_array_add_internal(struct socket_array* sa, int fd, socket_t s
|
||||
sa->capacity = new_capacity;
|
||||
}
|
||||
|
||||
// Check if FD already exists
|
||||
if (sa->fd_to_index[fd] != -1) return -1; // FD already exists
|
||||
|
||||
// Find first u_free slot
|
||||
int index = -1;
|
||||
for (int i = 0; i < sa->capacity; i++) {
|
||||
if (!sa->sockets[i].active) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
// Check if FD already has a node — reuse inactive slot if present
|
||||
int index;
|
||||
int existing = sa->fd_to_index[fd];
|
||||
if (existing != -1) {
|
||||
if (sa->sockets[existing].active) return -1; // FD уже занят активной нодой — ошибка
|
||||
index = existing; // переиспользуем неактивный слот (после socket_array_remove)
|
||||
} else {
|
||||
// Find first free slot
|
||||
index = -1;
|
||||
for (int i = 0; i < sa->capacity; i++) {
|
||||
if (!sa->sockets[i].active) { index = i; break; }
|
||||
}
|
||||
if (index == -1) return -1; // No free slots
|
||||
sa->fd_to_index[fd] = index; // новая привязка fd→слот
|
||||
}
|
||||
|
||||
if (index == -1) return -1; // No u_free slots (shouldn't happen)
|
||||
|
||||
// Add the socket
|
||||
sa->sockets[index].fd = fd;
|
||||
sa->sockets[index].sock = sock;
|
||||
@@ -194,8 +199,8 @@ static int socket_array_add_internal(struct socket_array* sa, int fd, socket_t s
|
||||
sa->sockets[index].active = 1;
|
||||
sa->sockets[index].enable_read = (read_cbk_fd != NULL || read_cbk_sock != NULL) ? 1 : 0;
|
||||
sa->sockets[index].enable_write = (write_cbk_fd != NULL || write_cbk_sock != NULL) ? 1 : 0;
|
||||
sa->sockets[index].gen = ++sa->gen_counter;
|
||||
|
||||
sa->fd_to_index[fd] = index;
|
||||
sa->index_to_fd[index] = fd;
|
||||
sa->active_indices[sa->count] = index; // Add to active list
|
||||
sa->count++;
|
||||
@@ -246,7 +251,8 @@ static int socket_array_remove(struct socket_array* sa, int fd) {
|
||||
sa->sockets[index].user_data = NULL;
|
||||
sa->sockets[index].enable_read = 0;
|
||||
sa->sockets[index].enable_write = 0;
|
||||
sa->fd_to_index[fd] = -1;
|
||||
// fd_to_index[fd] сохраняем — stale epoll события найдут неактивную ноду и пропустятся.
|
||||
// При переиспользовании fd, socket_array_add_internal перезапишет этот же слот.
|
||||
sa->index_to_fd[index] = -1;
|
||||
|
||||
// Remove from active_indices by swapping with last element
|
||||
@@ -651,14 +657,14 @@ void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, s
|
||||
ua->poll_fds_dirty = 1; // Mark poll_fds as needing rebuild
|
||||
|
||||
#if HAS_EPOLL
|
||||
// Add to epoll if using epoll
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = 0;
|
||||
if (read_cbk) ev.events |= EPOLLIN;
|
||||
if (write_cbk) ev.events |= EPOLLOUT;
|
||||
// Use level-triggered mode (default) for compatibility with UDP sockets
|
||||
ev.data.fd = fd;
|
||||
// Add to epoll if using epoll
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = 0;
|
||||
if (read_cbk) ev.events |= EPOLLIN;
|
||||
if (write_cbk) ev.events |= EPOLLOUT;
|
||||
// Embed gen in upper 32 bits of data.u64 for stale-event detection
|
||||
ev.data.u64 = ((uint64_t)ua->sockets->sockets[index].gen << 32) | (uint32_t)fd;
|
||||
|
||||
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
||||
// Failed to add to epoll - remove from socket array and return error
|
||||
@@ -721,7 +727,7 @@ void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t
|
||||
#else
|
||||
int fd = sock;
|
||||
#endif
|
||||
ev.data.fd = fd;
|
||||
ev.data.u64 = ((uint64_t)ua->sockets->sockets[index].gen << 32) | (uint32_t)fd;
|
||||
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
||||
socket_array_remove(ua->sockets, fd);
|
||||
ua->socket_alloc_count--;
|
||||
@@ -786,13 +792,13 @@ err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->fd;
|
||||
}
|
||||
if (node->except_cbk) ev.events |= EPOLLPRI;
|
||||
ev.data.u64 = ((uint64_t)node->gen << 32) | (uint32_t)(node->type == SOCKET_NODE_TYPE_SOCK ? (int)node->sock : node->fd);
|
||||
#ifdef _WIN32
|
||||
int efd = (int)(intptr_t)ev.data.fd;
|
||||
int efd = (int)(intptr_t)(node->type == SOCKET_NODE_TYPE_SOCK ? node->sock : node->fd);
|
||||
#else
|
||||
int efd = ev.data.fd;
|
||||
int efd = (node->type == SOCKET_NODE_TYPE_SOCK ? (int)node->sock : node->fd);
|
||||
#endif
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
|
||||
}
|
||||
@@ -819,17 +825,16 @@ err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->sock;
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->fd;
|
||||
}
|
||||
if (node->except_cbk) ev.events |= EPOLLPRI;
|
||||
ev.data.u64 = ((uint64_t)node->gen << 32) | (uint32_t)(node->type == SOCKET_NODE_TYPE_SOCK ? (int)node->sock : node->fd);
|
||||
#ifdef _WIN32
|
||||
int efd = (int)(intptr_t)ev.data.fd;
|
||||
int efd = (int)(intptr_t)(node->type == SOCKET_NODE_TYPE_SOCK ? node->sock : node->fd);
|
||||
#else
|
||||
int efd = ev.data.fd;
|
||||
int efd = (node->type == SOCKET_NODE_TYPE_SOCK ? (int)node->sock : node->fd);
|
||||
#endif
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
|
||||
}
|
||||
@@ -908,22 +913,20 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
|
||||
|
||||
// Process events from epoll (Linux only)
|
||||
#if HAS_EPOLL
|
||||
static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events, int n_events) {
|
||||
for (int i = 0; i < n_events; i++) {
|
||||
// Check if this is the wakeup fd (data.fd is -1)
|
||||
static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events, int n_events) {
|
||||
for (int i = 0; i < n_events; i++) {
|
||||
// Wakeup fd detected by data.fd < 0 (lower 32 bits of data.u64 = -1)
|
||||
if (events[i].data.fd < 0) {
|
||||
if (events[i].events & EPOLLIN) {
|
||||
drain_wakeup_pipe(ua);
|
||||
}
|
||||
if (events[i].events & EPOLLIN) { drain_wakeup_pipe(ua); }
|
||||
continue;
|
||||
}
|
||||
|
||||
// Socket event — save node fields locally: callbacks may realloc
|
||||
// the socket array, invalidating `node`.
|
||||
struct socket_node* node = socket_array_get(ua->sockets, events[i].data.fd);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d ev=0x%x%s", events[i].data.fd, events[i].events,
|
||||
!node || !node->active ? " (inactive)" : "");
|
||||
int fd = (int)(events[i].data.u64 & 0xFFFFFFFF);
|
||||
uint16_t ev_gen = (uint16_t)(events[i].data.u64 >> 32);
|
||||
|
||||
struct socket_node* node = socket_array_get(ua->sockets, fd);
|
||||
if (!node || !node->active) continue;
|
||||
if (node->gen != ev_gen) { DEBUG_ERROR(DEBUG_CATEGORY_UASYNC, "STALE epoll fd=%d ev_gen=%u node_gen=%u", fd, ev_gen, node->gen); continue; }
|
||||
int local_fd = node->fd;
|
||||
socket_t local_sock = node->sock;
|
||||
int local_type = node->type;
|
||||
|
||||
Reference in new issue
Block a user