diff options
53 files changed, 2521 insertions, 228 deletions
diff --git a/elf/Makefile b/elf/Makefile index 0792b57678..00622ace9d 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -1328,11 +1328,8 @@ $(objpfx)dl-allobjs.os: $(all-rtld-routines:%=$(objpfx)%.os) # discovery mechanism is not compatible with the libc implementation # when compiled for libc. rtld-stubbed-symbols = \ - __GI___pthread_disable_asynccancel \ - __GI___pthread_enable_asynccancel \ __libc_assert_fail \ - __pthread_disable_asynccancel \ - __pthread_enable_asynccancel \ + __syscall_cancel \ calloc \ free \ malloc \ diff --git a/nptl/Makefile b/nptl/Makefile index c4c27e0d23..bf4c29b6ed 100644 --- a/nptl/Makefile +++ b/nptl/Makefile @@ -204,6 +204,7 @@ routines = \ sem_timedwait \ sem_unlink \ sem_wait \ + syscall_cancel \ tpp \ unwind \ vars \ @@ -235,7 +236,8 @@ CFLAGS-pthread_setcanceltype.c += -fexceptions -fasynchronous-unwind-tables # These are internal functions which similar functionality as setcancelstate # and setcanceltype. -CFLAGS-cancellation.c += -fasynchronous-unwind-tables +CFLAGS-cancellation.c += -fexceptions -fasynchronous-unwind-tables +CFLAGS-syscall_cancel.c += -fexceptions -fasynchronous-unwind-tables # Calling pthread_exit() must cause the registered cancel handlers to # be executed. Therefore exceptions have to be thrown through this @@ -279,6 +281,7 @@ tests = \ tst-cancel7 \ tst-cancel17 \ tst-cancel24 \ + tst-cancel31 \ tst-cond26 \ tst-context1 \ tst-default-attr \ @@ -404,7 +407,10 @@ xtests += tst-eintr1 test-srcs = tst-oddstacklimit -gen-as-const-headers = unwindbuf.sym +gen-as-const-headers = \ + descr-const.sym \ + unwindbuf.sym \ + # gen-as-const-headers gen-py-const-headers := nptl_lock_constants.pysym pretty-printers := nptl-printers.py diff --git a/nptl/cancellation.c b/nptl/cancellation.c index 7ce60e70d0..e71008b58b 100644 --- a/nptl/cancellation.c +++ b/nptl/cancellation.c @@ -18,74 +18,93 @@ #include <setjmp.h> #include <stdlib.h> #include "pthreadP.h" -#include <futex-internal.h> - -/* The next two functions are similar to pthread_setcanceltype() but - more specialized for the use in the cancelable functions like write(). - They do not need to check parameters etc. These functions must be - AS-safe, with the exception of the actual cancellation, because they - are called by wrappers around AS-safe functions like write().*/ -int -__pthread_enable_asynccancel (void) +/* Called by the INTERNAL_SYSCALL_CANCEL macro, check for cancellation and + returns the syscall value or its negative error code. */ +long int +__internal_syscall_cancel (__syscall_arg_t a1, __syscall_arg_t a2, + __syscall_arg_t a3, __syscall_arg_t a4, + __syscall_arg_t a5, __syscall_arg_t a6, + __SYSCALL_CANCEL7_ARG_DEF + __syscall_arg_t nr) { - struct pthread *self = THREAD_SELF; - int oldval = atomic_load_relaxed (&self->cancelhandling); + long int result; + struct pthread *pd = THREAD_SELF; - while (1) + /* If cancellation is not enabled, call the syscall directly and also + for thread terminatation to avoid call __syscall_do_cancel while + executing cleanup handlers. */ + int ch = atomic_load_relaxed (&pd->cancelhandling); + if (SINGLE_THREAD_P || !cancel_enabled (ch) || cancel_exiting (ch)) { - int newval = oldval | CANCELTYPE_BITMASK; - - if (newval == oldval) - break; + result = INTERNAL_SYSCALL_NCS_CALL (nr, a1, a2, a3, a4, a5, a6 + __SYSCALL_CANCEL7_ARCH_ARG7); + if (INTERNAL_SYSCALL_ERROR_P (result)) + return -INTERNAL_SYSCALL_ERRNO (result); + return result; + } - if (atomic_compare_exchange_weak_acquire (&self->cancelhandling, - &oldval, newval)) - { - if (cancel_enabled_and_canceled_and_async (newval)) - { - self->result = PTHREAD_CANCELED; - __do_cancel (); - } + /* Call the arch-specific entry points that contains the globals markers + to be checked by SIGCANCEL handler. */ + result = __syscall_cancel_arch (&pd->cancelhandling, nr, a1, a2, a3, a4, a5, + a6 __SYSCALL_CANCEL7_ARCH_ARG7); - break; - } - } + /* If the cancellable syscall was interrupted by SIGCANCEL and it has no + side-effect, cancel the thread if cancellation is enabled. */ + ch = atomic_load_relaxed (&pd->cancelhandling); + /* The behaviour here assumes that EINTR is returned only if there are no + visible side effects. POSIX Issue 7 has not yet provided any stronger + language for close, and in theory the close syscall could return EINTR + and leave the file descriptor open (conforming and leaks). It expects + that no such kernel is used with glibc. */ + if (result == -EINTR && cancel_enabled_and_canceled (ch)) + __syscall_do_cancel (); - return oldval; + return result; } -libc_hidden_def (__pthread_enable_asynccancel) -/* See the comment for __pthread_enable_asynccancel regarding - the AS-safety of this function. */ -void -__pthread_disable_asynccancel (int oldtype) +/* Called by the SYSCALL_CANCEL macro, check for cancellation and return the + syscall expected success value (usually 0) or, in case of failure, -1 and + sets errno to syscall return value. */ +long int +__syscall_cancel (__syscall_arg_t a1, __syscall_arg_t a2, + __syscall_arg_t a3, __syscall_arg_t a4, + __syscall_arg_t a5, __syscall_arg_t a6, + __SYSCALL_CANCEL7_ARG_DEF __syscall_arg_t nr) { - /* If asynchronous cancellation was enabled before we do not have - anything to do. */ - if (oldtype & CANCELTYPE_BITMASK) - return; + int r = __internal_syscall_cancel (a1, a2, a3, a4, a5, a6, + __SYSCALL_CANCEL7_ARG nr); + return __glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (r)) + ? SYSCALL_ERROR_LABEL (INTERNAL_SYSCALL_ERRNO (r)) + : r; +} +/* Called by __syscall_cancel_arch or function above start the thread + cancellation. */ +_Noreturn void +__syscall_do_cancel (void) +{ struct pthread *self = THREAD_SELF; - int newval; + + /* Disable thread cancellation to avoid cancellable entrypoints calling + __syscall_do_cancel recursively. We atomic load relaxed to check the + state of cancelhandling, there is no particular ordering requirement + between the syscall call and the other thread setting our cancelhandling + with a atomic store acquire. + + POSIX Issue 7 notes that the cancellation occurs asynchronously on the + target thread, that implies there is no ordering requirements. It does + not need a MO release store here. */ int oldval = atomic_load_relaxed (&self->cancelhandling); - do + while (1) { - newval = oldval & ~CANCELTYPE_BITMASK; + int newval = oldval | CANCELSTATE_BITMASK; + if (oldval == newval) + break; + if (atomic_compare_exchange_weak_acquire (&self->cancelhandling, + &oldval, newval)) + break; } - while (!atomic_compare_exchange_weak_acquire (&self->cancelhandling, - &oldval, newval)); - /* We cannot return when we are being canceled. Upon return the - thread might be things which would have to be undone. The - following loop should loop until the cancellation signal is - delivered. */ - while (__glibc_unlikely ((newval & (CANCELING_BITMASK | CANCELED_BITMASK)) - == CANCELING_BITMASK)) - { - futex_wait_simple ((unsigned int *) &self->cancelhandling, newval, - FUTEX_PRIVATE); - newval = atomic_load_relaxed (&self->cancelhandling); - } + __do_cancel (PTHREAD_CANCELED); } -libc_hidden_def (__pthread_disable_asynccancel) diff --git a/nptl/cleanup_defer.c b/nptl/cleanup_defer.c index dc08eda003..db32c4fc26 100644 --- a/nptl/cleanup_defer.c +++ b/nptl/cleanup_defer.c @@ -82,10 +82,7 @@ ___pthread_unregister_cancel_restore (__pthread_unwind_buf_t *buf) &cancelhandling, newval)); if (cancel_enabled_and_canceled (cancelhandling)) - { - self->result = PTHREAD_CANCELED; - __do_cancel (); - } + __do_cancel (PTHREAD_CANCELED); } } versioned_symbol (libc, ___pthread_unregister_cancel_restore, diff --git a/nptl/descr-const.sym b/nptl/descr-const.sym new file mode 100644 index 0000000000..8608248354 --- /dev/null +++ b/nptl/descr-const.sym @@ -0,0 +1,6 @@ +#include <tls.h> + +-- Not strictly offsets, these values are using thread cancellation by arch +-- specific cancel entrypoint. +TCB_CANCELED_BIT CANCELED_BIT +TCB_CANCELED_BITMASK CANCELED_BITMASK diff --git a/nptl/descr.h b/nptl/descr.h index 8cef95810c..65d3baaee3 100644 --- a/nptl/descr.h +++ b/nptl/descr.h @@ -426,6 +426,24 @@ struct pthread } __attribute ((aligned (TCB_ALIGNMENT))); static inline bool +cancel_enabled (int value) +{ + return (value & CANCELSTATE_BITMASK) == 0; +} + +static inline bool +cancel_async_enabled (int value) +{ + return (value & CANCELTYPE_BITMASK) != 0; +} + +static inline bool +cancel_exiting (int value) +{ + return (value & EXITING_BITMASK) != 0; +} + +static inline bool cancel_enabled_and_canceled (int value) { return (value & (CANCELSTATE_BITMASK | CANCELED_BITMASK | EXITING_BITMASK diff --git a/nptl/libc-cleanup.c b/nptl/libc-cleanup.c index fe042c85aa..20d746cbb7 100644 --- a/nptl/libc-cleanup.c +++ b/nptl/libc-cleanup.c @@ -69,10 +69,7 @@ __libc_cleanup_pop_restore (struct _pthread_cleanup_buffer *buffer) &cancelhandling, newval)); if (cancel_enabled_and_canceled (cancelhandling)) - { - self->result = PTHREAD_CANCELED; - __do_cancel (); - } + __do_cancel (PTHREAD_CANCELED); } } libc_hidden_def (__libc_cleanup_pop_restore) diff --git a/nptl/pthread_cancel.c b/nptl/pthread_cancel.c index 69701db6f9..012c4ebeb8 100644 --- a/nptl/pthread_cancel.c +++ b/nptl/pthread_cancel.c @@ -23,6 +23,7 @@ #include <sysdep.h> #include <unistd.h> #include <unwind-link.h> +#include <cancellation-pc-check.h> #include <stdio.h> #include <gnu/lib-names.h> #include <sys/single_threaded.h> @@ -40,31 +41,16 @@ 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 |
