nptl: Check if thread is already terminated in sigcancel_handler (BZ 32782)

The SIGCANCEL signal handler should not issue __syscall_do_cancel,
which calls __do_cancel and __pthread_unwind, if the cancellation
is already in proces (and libgcc unwind is not reentrant).  Any
cancellation signal received after is ignored.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.

Tested-by: Aurelien Jarno <aurelien@aurel32.net>
Reviewed-by: Florian Weimer <fweimer@redhat.com>
This commit is contained in:
Adhemerval Zanella 2025-03-12 10:59:17 -03:00
parent dbc5a50d12
commit 360cce0b06
4 changed files with 83 additions and 6 deletions

View File

@ -1,6 +1,7 @@
## args: double
## ret: double
## includes: math.h
## name: workload-random
-0x1.79ea722d33e33p-9
-0x1.94d4e0c3df9bcp3
-0x1.b63e91ff711e0p0

View File

@ -41,15 +41,17 @@ sigcancel_handler (int sig, siginfo_t *si, void *ctx)
|| si->si_code != SI_TKILL)
return;
/* Check if asynchronous cancellation mode is set or if interrupted
instruction pointer falls within the cancellable syscall bridge. For
interruptable syscalls with external side-effects (i.e. partial reads),
the kernel will set the IP to after __syscall_cancel_arch_end, thus
disabling the cancellation and allowing the process to handle such
/* Check if asynchronous cancellation mode is set and cancellation is not
already in progress, or if interrupted instruction pointer falls within
the cancellable syscall bridge.
For interruptable syscalls with external side-effects (i.e. partial
reads), the kernel will set the IP to after __syscall_cancel_arch_end,
thus disabling the cancellation and allowing the process to handle such
conditions. */
struct pthread *self = THREAD_SELF;
int oldval = atomic_load_relaxed (&self->cancelhandling);
if (cancel_async_enabled (oldval) || cancellation_pc_check (ctx))
if (cancel_enabled_and_canceled_and_async (oldval)
|| cancellation_pc_check (ctx))
__syscall_do_cancel ();
}

View File

@ -106,6 +106,7 @@ tests += \
tst-cancel28 \
tst-cancel29 \
tst-cancel30 \
tst-cancel32 \
tst-cleanup0 \
tst-cleanup1 \
tst-cleanup2 \

View File

@ -0,0 +1,73 @@
/* Check if pthread_setcanceltype disables asynchronous cancellation
once cancellation happens (BZ 32782)
Copyright (C) 2025 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
/* The pthread_setcanceltype is a cancellation entrypoint, and if
asynchronous is enabled and the cancellation starts (on the second
pthread_setcanceltype call), the asynchronous should not restart
the process. */
#include <support/xthread.h>
#define NITER 1000
#define NTHREADS 8
static void
tf_cleanup (void *arg)
{
}
static void *
tf (void *closure)
{
pthread_cleanup_push (tf_cleanup, NULL);
for (;;)
{
/* The only possible failure for pthread_setcanceltype is an
invalid state type. */
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, NULL);
}
pthread_cleanup_pop (1);
return NULL;
}
static void
poll_threads (int nthreads)
{
pthread_t thr[nthreads];
for (int i = 0; i < nthreads; i++)
thr[i] = xpthread_create (NULL, tf, NULL);
for (int i = 0; i < nthreads; i++)
xpthread_cancel (thr[i]);
for (int i = 0; i < nthreads; i++)
xpthread_join (thr[i]);
}
static int
do_test (void)
{
for (int k = 0; k < NITER; k++)
poll_threads (NTHREADS);
return 0;
}
#include <support/test-driver.c>