diff --git a/include/stdlib.h b/include/stdlib.h
index ffcefd7b85..a2431f9139 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -315,6 +315,10 @@ extern __typeof (unsetenv) unsetenv attribute_hidden;
extern __typeof (__strtoul_internal) __strtoul_internal attribute_hidden;
# endif
-#endif
+/* Coordinate locks with fork to make abort AS-safe. */
+void __abort_fork_lock (void) attribute_hidden;
+void __abort_fork_unlock (void) attribute_hidden;
+
+#endif /* !defined _ISOMAC */
#endif /* include/stdlib.h */
diff --git a/manual/startup.texi b/manual/startup.texi
index 21c48cd037..e38cdc0e28 100644
--- a/manual/startup.texi
+++ b/manual/startup.texi
@@ -992,10 +992,7 @@ for this function is in @file{stdlib.h}.
@deftypefun void abort (void)
@standards{ISO, stdlib.h}
-@safety{@prelim{}@mtsafe{}@asunsafe{@asucorrupt{}}@acunsafe{@aculock{} @acucorrupt{}}}
-@c The implementation takes a recursive lock and attempts to support
-@c calls from signal handlers, but if we're in the middle of flushing or
-@c using streams, we may encounter them in inconsistent states.
+@safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@aculock{} @acucorrupt{}}}
The @code{abort} function causes abnormal program termination. This
does not execute cleanup functions registered with @code{atexit} or
@code{on_exit}.
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 4615f6dfe7..cd470e53ac 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -87,7 +87,7 @@ tests := tst-strtol tst-strtod testmb testrand testsort testdiv \
tst-makecontext-align test-bz22786 tst-strtod-nan-sign \
tst-swapcontext1 tst-setcontext4 tst-setcontext5 \
tst-setcontext6 tst-setcontext7 tst-setcontext8 \
- tst-setcontext9 tst-bz20544
+ tst-setcontext9 tst-bz20544 tst-threaded-fork-abort \
tests-internal := tst-strtod1i tst-strtod3 tst-strtod4 tst-strtod5i \
tst-tls-atexit tst-tls-atexit-nodelete
@@ -243,3 +243,5 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
$(evaluate-test)
$(objpfx)tst-makecontext: $(libdl)
+
+LDLIBS-tst-threaded-fork-abort = $(shared-thread-library)
diff --git a/stdlib/abort.c b/stdlib/abort.c
index df98782dd7..e7f74319d6 100644
--- a/stdlib/abort.c
+++ b/stdlib/abort.c
@@ -42,6 +42,17 @@ static int stage;
/* We should be prepared for multiple threads trying to run abort. */
__libc_lock_define_initialized_recursive (static, lock);
+void
+__abort_fork_lock (void)
+{
+ __libc_lock_lock_recursive (lock);
+}
+
+void
+__abort_fork_unlock (void)
+{
+ __libc_lock_unlock_recursive (lock);
+}
/* Cause an abnormal program termination with core-dump. */
void
diff --git a/stdlib/tst-threaded-fork-abort.c b/stdlib/tst-threaded-fork-abort.c
new file mode 100644
index 0000000000..67f0b09086
--- /dev/null
+++ b/stdlib/tst-threaded-fork-abort.c
@@ -0,0 +1,168 @@
+/* Verify abort is AS-safe (Bug 26275).
+ Copyright (C) 2020 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
+ . */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static void
+handle_abort (int sig)
+{
+ int wstatus;
+ pid_t ret;
+ /* The parent has called abort, and now we wait on the child to call
+ abort. If the child never calls abort because the abort is live
+ locked on the abort lock then the test times out with an error.
+ If the child calls abort we see that, capture it here, and exit
+ the test successfully. We loop because we might have called abort
+ before the child was created and so we have to try again. */
+ do
+ {
+ ret = wait (&wstatus);
+ }
+ while (ret == -1);
+
+ /* The child must have exited with SIGABRT or we fail the test. */
+ if (WIFSIGNALED (wstatus))
+ if (WTERMSIG (wstatus) == SIGABRT)
+ _exit (0);
+
+ /* The child exit condition is not what we expected. */
+ _exit (1);
+}
+
+static void *
+thread_abort (void *closure)
+{
+ pthread_barrier_t *barrier = (pthread_barrier_t *) closure;
+ xpthread_barrier_wait (barrier);
+ /* Attempt to abort. */
+ abort ();
+ return NULL;
+}
+
+static void *
+thread_fork (void *closure)
+{
+ pthread_barrier_t *barrier = (pthread_barrier_t *) closure;
+ pid_t child;
+
+ /* Bug 26275: In the child the abort hangs. This allows the parent
+ to wait for the child termination in its own SIGABRT handler.
+ We want to fork just after the aborting thread has taken the
+ abort handler lock, but we can't always time it right. */
+ xpthread_barrier_wait (barrier);
+ child = xfork ();
+
+ if (child == 0)
+ {
+ /* Reset default disposition for SIGABRT so our own abort will
+ terminate the child. */
+ signal (SIGABRT, SIG_DFL);
+ /* Without the fix this hangs because the forked child observes
+ the abort lock as held. */
+ abort ();
+ }
+
+ return NULL;
+}
+
+/* The goal of this test is to call fork from thread B while thread A is
+ in the process of aborting. Bug 26275 shows that if thread A is
+ holding the abort lock while thread B forks then thread B will be
+ unable to abort (livelock on the abort lock). The fix is for
+ thread B to take the abort lock during fork setup, and then release
+ it after the fork (in both child and parent). This test is
+ probabilistic and may not trigger the bug, but we run for long enough
+ that any issues during this abort/fork sequencing should show up in
+ long-term testing as this test failing every once in a while.
+
+ On a x86_64 VM (4 vCPU @ 3.5GHz) the test fails once every 100-600
+ seconds when run continuosly in a loop. We want this test to fail
+ with a probability of say 10% so develpers see it at least once in
+ a one or two week development period, or at least once during the
+ release window. To achieve that we run for at least 20 seconds,
+ and set the timeout at 30 seconds (gives time for a last iteration
+ to complete). */
+static void
+test_fork_abort (void *closure __attribute__ ((unused)))
+{
+ struct sigaction sact;
+ struct sigaction oact;
+ pthread_barrier_t barrier;
+
+ /* Wait for both threads to get in the right spot. */
+ xpthread_barrier_init (&barrier, NULL, 2);
+
+ /* Handle the abort. */
+ sact.sa_handler = handle_abort;
+ xsigaction (SIGABRT, &sact, &oact);
+
+ /* Start the abort thread. */
+ xpthread_create (NULL, thread_abort, &barrier);
+
+ /* Start the fork thread. */
+ xpthread_create (NULL, thread_fork, &barrier);
+
+ /* Wait indefinately. We exit via the abort handler. */
+ while (1)
+ pause ();
+}
+
+static int
+do_test (void)
+{
+ struct support_capture_subprocess result;
+ struct timespec before, after, running, end;
+
+ /* Total runtime is 20 seconds. */
+ end = make_timespec (20, 0);
+
+ /* Measure start time. */
+ xclock_gettime (CLOCK_MONOTONIC, &before);
+
+ /* Run test for 20 seconds. */
+ do
+ {
+ result = support_capture_subprocess (test_fork_abort, NULL);
+ support_capture_subprocess_check (&result, "test_fork_abort", 0,
+ sc_allow_none);
+ xclock_gettime (CLOCK_MONOTONIC, &after);
+ running = timespec_sub (support_timespec_normalize (after),
+ support_timespec_normalize (before));
+ }
+ while (TIMESPEC_BEFORE (running, end));
+
+ /* Success. */
+ return 0;
+}
+
+#define TIMEOUT 30
+#define TEST_FUNCTION do_test
+#include
diff --git a/support/timespec.c b/support/timespec.c
index edbdb165ec..3d2cdfca5b 100644
--- a/support/timespec.c
+++ b/support/timespec.c
@@ -27,18 +27,17 @@ test_timespec_before_impl (const char *file, int line,
const struct timespec left,
const struct timespec right)
{
- if (left.tv_sec > right.tv_sec
- || (left.tv_sec == right.tv_sec
- && left.tv_nsec > right.tv_nsec)) {
- support_record_failure ();
- const struct timespec diff = timespec_sub (left, right);
- printf ("%s:%d: %jd.%09jds not before %jd.%09jds "
- "(difference %jd.%09jds)\n",
- file, line,
- (intmax_t) left.tv_sec, (intmax_t) left.tv_nsec,
- (intmax_t) right.tv_sec, (intmax_t) right.tv_nsec,
- (intmax_t) diff.tv_sec, (intmax_t) diff.tv_nsec);
- }
+ if (!TIMESPEC_BEFORE(left, right))
+ {
+ support_record_failure ();
+ const struct timespec diff = timespec_sub (left, right);
+ printf ("%s:%d: %jd.%09jds not before %jd.%09jds "
+ "(difference %jd.%09jds)\n",
+ file, line,
+ (intmax_t) left.tv_sec, (intmax_t) left.tv_nsec,
+ (intmax_t) right.tv_sec, (intmax_t) right.tv_nsec,
+ (intmax_t) diff.tv_sec, (intmax_t) diff.tv_nsec);
+ }
}
void
diff --git a/support/timespec.h b/support/timespec.h
index 1a6775a882..280844f2c0 100644
--- a/support/timespec.h
+++ b/support/timespec.h
@@ -55,6 +55,11 @@ struct timespec support_timespec_normalize (struct timespec time);
int support_timespec_check_in_range (struct timespec expected, struct timespec observed,
double lower_bound, double upper_bound);
+/* True if left is before right, false otherwise. */
+#define TIMESPEC_BEFORE(left, right) \
+ ((left.tv_sec < right.tv_sec) \
+ || (left.tv_sec == right.tv_sec \
+ && left.tv_nsec < right.tv_nsec))
/* Check that the timespec on the left represents a time before the
time on the right. */
diff --git a/sysdeps/nptl/fork.c b/sysdeps/nptl/fork.c
index 5091a000e3..8fdc6589e5 100644
--- a/sysdeps/nptl/fork.c
+++ b/sysdeps/nptl/fork.c
@@ -66,6 +66,11 @@ __libc_fork (void)
{
_IO_list_lock ();
+ /* Acquire the abort lock. We need to prevent other threads
+ from aborting while we fork. We must keep abort AS-safe
+ and to do that we need to own the lock in the child. */
+ __abort_fork_lock ();
+
/* Acquire malloc locks. This needs to come last because fork
handlers may use malloc, and the libio list lock has an
indirect malloc dependency as well (via the getdelim
@@ -113,6 +118,9 @@ __libc_fork (void)
/* Release malloc locks. */
call_function_static_weak (__malloc_fork_unlock_child);
+ /* Release the abort lock. */
+ __abort_fork_unlock ();
+
/* Reset the file list. These are recursive mutexes. */
fresetlockfiles ();
@@ -134,6 +142,9 @@ __libc_fork (void)
/* Release malloc locks, parent process variant. */
call_function_static_weak (__malloc_fork_unlock_parent);
+ /* Release the abort lock. */
+ __abort_fork_unlock ();
+
/* We execute this even if the 'fork' call failed. */
_IO_list_unlock ();
}