From 9718440678717fdb7f9cd1821d3c331748ed9f98 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 2 Sep 2026 13:38:08 -0700 Subject: [PATCH] SIGEV_THREAD timers: fix deadlock when timer_create syscall fails sem_wait is a cancellation point. If the parent thread in timer_create sets td->cancel = 1 before the child thread reaches sem_wait then the thread will exit and sem_post will never be called, causing the parent thread to deadlock in sem_wait. 3ad3fa962efee12067d68c3405a537dce156a7ac set td->cancel = 1 to fix a thread leak when the syscall failed. The switch to two-way semaphores cde213f9c3ac1aa168581222edee6a6642113323 allows checking if self->timer_id is -1 instead of self->cancel because timer_delete can't have been called before the sem_post allows the parent thread to return from timer_create. --- src/time/timer_create.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/time/timer_create.c b/src/time/timer_create.c index cc6c2236..a89f45af 100644 --- a/src/time/timer_create.c +++ b/src/time/timer_create.c @@ -45,19 +45,28 @@ static void *start(void *arg) pthread_t self = __pthread_self(); struct start_args *args = arg; jmp_buf jb; + int cs; void (*notify)(union sigval) = args->sev->sigev_notify_function; union sigval val = args->sev->sigev_value; + int cancel = 0; - /* The two-way semaphore synchronization ensures that we see - * self->cancel set by the parent if timer creation failed or - * self->timer_id if it succeeded, and informs the parent that - * we are done accessing the arguments so that the parent can - * proceed past their block lifetime. */ + /* Waiting on sem1 ensures we see self->timer_id. */ while (sem_wait(&args->sem1)); + + /* If self->timer_id is -1 while the parent thread is still in + * timer_create (before the sem_post to sem2) then the timer was + * never created. If it is -1 later after the sem_post then + * it was cancelled via timer_delete. */ + if (self->timer_id < 0) + cancel = 1; + + /* Incrementing sem2 informs the parent that we are done checking + * the initial value of self->timer_id, and accessing the arguments + * so that the parent can proceed past their block lifetime. */ sem_post(&args->sem2); - if (self->cancel) + if (cancel) return 0; for (;;) { siginfo_t si; @@ -135,7 +144,6 @@ int timer_create(clockid_t clk, struct sigevent *restrict evp, timer_t *restrict ksev.sigev_tid = td->tid; if (syscall(SYS_timer_create, clk, &ksev, &timerid) < 0) { timerid = -1; - td->cancel = 1; } td->timer_id = timerid; sem_post(&args.sem1); -- 2.55.0.979.g7e5102b832-goog