Browse Source

ll_queue: queue_waiter_cancel отменяет отложенный call_soon при internal==NULL (фикс UAF waiter_defer)

proxy
evgeny 1 week ago
parent
commit
44d4265084
  1. 9
      lib/ll_queue.c
  2. 27
      tests/test_ll_queue.c

9
lib/ll_queue.c

@ -704,9 +704,14 @@ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
}
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
if (!q || !h || !h->internal) return;
if (!q || !h) return;
/* Отложенный вызов (waiter_defer) может висеть и при internal==NULL:
* queue_waiter_wait по ветке «очередь свободна» планирует call_soon_id,
* не регистрируя waiter. Ранний выход по !internal оставлял бы висячий
* callback с arg=handle, который после teardown разыменовывал бы freed-память. */
if (h->call_soon_id) { uasync_call_soon_cancel(q->ua, h->call_soon_id); h->call_soon_id = NULL; }
if (!h->internal) return;
struct queue_waiter* waiter = h->internal;
struct queue_waiter* prev = NULL;
struct queue_waiter* curr = q->waiter_head;

27
tests/test_ll_queue.c

@ -281,6 +281,32 @@ static void test_waiter_cancel(void) {
PASS();
}
static void test_waiter_cancel_deferred(void) {
TEST("waiter cancel deferred call_soon (internal==NULL)");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wcd");
queue_set_threshold(q, 0, 0);
queue_set_waiter_defer(q, 1);
int called = 0;
struct queue_waiter_handle h = {0};
int ret = queue_waiter_wait(q, &h, waiter_cb, &called);
ASSERT(ret == 1, "immediate branch (empty queue)");
ASSERT(h.internal == NULL, "no waiter registered (deferred path)");
ASSERT(h.call_soon_id != NULL, "deferred call scheduled");
/* До фикса: queue_waiter_cancel выходил по !internal, call_soon_id оставался
* висеть и deferred-колбэк срабатывал позже (UAF после teardown). */
queue_waiter_cancel(q, &h);
ASSERT(h.call_soon_id == NULL, "call_soon_id cleared by cancel");
for (int i = 0; i < 10; i++) uasync_poll(ua, 1);
ASSERT_EQ(called, 0, "deferred callback cancelled (did not fire)");
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_queue_free(void) {
TEST("queue_free clears waiters");
struct UASYNC *ua = uasync_create();
@ -454,6 +480,7 @@ int main(void) {
test_waiter();
test_waiter_multiple();
test_waiter_cancel();
test_waiter_cancel_deferred();
test_waiter_queue_free();
test_waiter_threshold();
test_waiter_reuse();

Loading…
Cancel
Save