From 44d42650843f36fd62500205e3154e136e2c4e36 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 26 Sep 2026 18:58:27 +0300 Subject: [PATCH] =?UTF-8?q?ll=5Fqueue:=20queue=5Fwaiter=5Fcancel=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BC=D0=B5=D0=BD=D1=8F=D0=B5=D1=82=20=D0=BE=D1=82=D0=BB?= =?UTF-8?q?=D0=BE=D0=B6=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20call=5Fsoon=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20internal=3D=3DNULL=20(=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D1=81=20UAF=20waiter=5Fdefer)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ll_queue.c | 9 +++++++-- tests/test_ll_queue.c | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/ll_queue.c b/lib/ll_queue.c index d8c557d0..dd5a144c 100644 --- a/lib/ll_queue.c +++ b/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; diff --git a/tests/test_ll_queue.c b/tests/test_ll_queue.c index 5a479891..41935a7a 100644 --- a/tests/test_ll_queue.c +++ b/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();