From 56290d6e762c1194547e73ff0b948cd79d3a1e03 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Thu, 12 May 2016 08:54:17 +0200 Subject: Increase fork signal safety for single-threaded processes [BZ #19703] This provides a band-aid and addresses the scenario where fork is called from a signal handler while the process is in the malloc subsystem (or has acquired the libio list lock). It does not address the general issue of async-signal-safety of fork; multi-threaded processes are not covered, and some glibc subsystems have fork handlers which are not async-signal-safe. --- malloc/Makefile | 3 +- malloc/tst-mallocfork2.c | 212 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 malloc/tst-mallocfork2.c (limited to 'malloc') diff --git a/malloc/Makefile b/malloc/Makefile index 3283f4f75f..fa1730ecb7 100644 --- a/malloc/Makefile +++ b/malloc/Makefile @@ -29,7 +29,8 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \ tst-malloc-usable tst-realloc tst-posix_memalign \ tst-pvalloc tst-memalign tst-mallopt tst-scratch_buffer \ tst-malloc-backtrace tst-malloc-thread-exit \ - tst-malloc-thread-fail tst-malloc-fork-deadlock + tst-malloc-thread-fail tst-malloc-fork-deadlock \ + tst-mallocfork2 test-srcs = tst-mtrace routines = malloc morecore mcheck mtrace obstack \ diff --git a/malloc/tst-mallocfork2.c b/malloc/tst-mallocfork2.c new file mode 100644 index 0000000000..a9e3e94aad --- /dev/null +++ b/malloc/tst-mallocfork2.c @@ -0,0 +1,212 @@ +/* Test case for async-signal-safe fork (with respect to malloc). + Copyright (C) 2016 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; see the file COPYING.LIB. If + not, see . */ + +/* This test will fail if the process is multi-threaded because we + only have an async-signal-safe fork in the single-threaded case + (where we skip acquiring the malloc heap locks). + + This test only checks async-signal-safety with regards to malloc; + other, more rarely-used glibc subsystems could have locks which + still make fork unsafe, even in single-threaded processes. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* How many malloc objects to keep arond. */ +enum { malloc_objects = 1009 }; + +/* The maximum size of an object. */ +enum { malloc_maximum_size = 70000 }; + +/* How many signals need to be delivered before the test exits. */ +enum { signal_count = 1000 }; + + +/* Process ID of the subprocess which sends SIGUSR1 signals. */ +static pid_t sigusr1_sender_pid; + +/* Set to 1 if SIGUSR1 is received. Used to detect a signal during + malloc/free. */ +static volatile sig_atomic_t sigusr1_received; + +/* Periodically set to 1, to indicate that the process is making + progress. Checked by liveness_signal_handler. */ +static volatile sig_atomic_t progress_indicator = 1; + +/* Write the message to standard output. Usable from signal + handlers. */ +static void +write_message (const char *str) +{ + write (STDOUT_FILENO, str, strlen (str)); +} + +static void +sigusr1_handler (int signo) +{ + /* Let the main program make progress, by temporarily suspending + signals from the subprocess. */ + if (sigusr1_received) + return; + if (kill (sigusr1_sender_pid, SIGSTOP) != 0) + { + write_message ("error: kill (SIGSTOP)\n"); + abort (); + } + sigusr1_received = 1; + + /* Perform a fork with a trivial subprocess. */ + pid_t pid = fork (); + if (pid == -1) + { + write_message ("error: fork\n"); + abort (); + } + if (pid == 0) + _exit (0); + int status; + int ret = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)); + if (ret < 0) + { + write_message ("error: waitpid\n"); + abort (); + } + if (status != 0) + { + write_message ("error: unexpected exit status from subprocess\n"); + abort (); + } +} + +static void +liveness_signal_handler (int signo) +{ + if (progress_indicator) + progress_indicator = 0; + else + write_message ("warning: process seems to be stuck\n"); +} + +static void +__attribute__ ((noreturn)) +signal_sender (int signo, bool sleep) +{ + pid_t target = getppid (); + while (true) + { + if (kill (target, signo) != 0) + { + dprintf (STDOUT_FILENO, "error: kill: %m\n"); + abort (); + } + if (sleep) + usleep (1 * 1000 * 1000); + } +} + +static int +do_test (void) +{ + struct sigaction action = + { + .sa_handler = sigusr1_handler, + }; + sigemptyset (&action.sa_mask); + + if (sigaction (SIGUSR1, &action, NULL) != 0) + { + printf ("error: sigaction: %m"); + return 1; + } + + action.sa_handler = liveness_signal_handler; + if (sigaction (SIGUSR2, &action, NULL) != 0) + { + printf ("error: sigaction: %m"); + return 1; + } + + pid_t sigusr2_sender_pid = fork (); + if (sigusr2_sender_pid == 0) + signal_sender (SIGUSR2, true); + sigusr1_sender_pid = fork (); + if (sigusr1_sender_pid == 0) + signal_sender (SIGUSR1, false); + + void *objects[malloc_objects] = {}; + unsigned signals = 0; + unsigned seed = 1; + time_t last_report = 0; + while (signals < signal_count) + { + progress_indicator = 1; + int slot = rand_r (&seed) % malloc_objects; + size_t size = rand_r (&seed) % malloc_maximum_size; + if (kill (sigusr1_sender_pid, SIGCONT) != 0) + { + printf ("error: kill (SIGCONT): %m\n"); + signal (SIGUSR1, SIG_IGN); + kill (sigusr1_sender_pid, SIGKILL); + kill (sigusr2_sender_pid, SIGKILL); + return 1; + } + sigusr1_received = false; + free (objects[slot]); + objects[slot] = malloc (size); + if (sigusr1_received) + { + ++signals; + time_t current = time (0); + if (current != last_report) + { + printf ("info: SIGUSR1 signal count: %u\n", signals); + last_report = current; + } + } + if (objects[slot] == NULL) + { + printf ("error: malloc: %m\n"); + signal (SIGUSR1, SIG_IGN); + kill (sigusr1_sender_pid, SIGKILL); + kill (sigusr2_sender_pid, SIGKILL); + return 1; + } + } + + /* Clean up allocations. */ + for (int slot = 0; slot < malloc_objects; ++slot) + free (objects[slot]); + + /* Terminate the signal-sending subprocess. The SIGUSR1 handler + should no longer run because it uses sigusr1_sender_pid. */ + signal (SIGUSR1, SIG_IGN); + kill (sigusr1_sender_pid, SIGKILL); + kill (sigusr2_sender_pid, SIGKILL); + + return 0; +} + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" -- cgit 1.4.1