Home > database >  Was abort() not async-signal-safe in glibc versions < 2.27?
Was abort() not async-signal-safe in glibc versions < 2.27?

Time:12-12

I am reading about signal safety in the manpages, and abort is one of the async signal safe functions as required by the POSIX standard. However, according to the manpages for abort, it says

Up until glibc 2.26, if the abort() function caused process termination, all open streams were closed and flushed (as with fclose(3)).

fclose is not listed as async-signal-safe (although close is). Does this mean that abort was not async-signal-safe in glibc versions before 2.27?

Another reason why I'm thinking this is that exit(3) is not async-signal-safe because it flushes the stdio buffers, which is what abort used to do.

CodePudding user response:

fclose is not listed as async-signal-safe (although close is). Does this mean that abort was not async-signal-safe in glibc versions before 2.27?

Yes, you are correct. abort was not async-signal-safe in the earlier versions. At a certain stage, it would [try to] fclose all open streams. And, it will do this from an internal signal handler it sets up.

A given stream could be locked or in an indeterminate state. The TL;DR is that abort was not async safe because fclose was [and is] not async safe.


It might help to be able to look at the older source. Here is the glibc source page:

https://www.gnu.org/software/libc/sources.html

On that page, we can clone the glibc git repository:

git clone https://sourceware.org/git/glibc.git
cd glibc
git checkout master

This will give us a fairly modern version.

But, with a slight change to the instructions there, we can checkout an earlier version:

git checkout release/2.25/master

Then, if we look at stdlib/abort.c, we see:

  /* Now close the streams which also flushes the output the user
     defined handler might has produced.  */
  if (stage == 4)
    {
        stage;
      __fcloseall ();
    }

In later versions, stage 4 is missing the __fcloseall call:

  /* Now try to abort using the system specific command.  */
  if (stage == 4)
    {
        stage;
      ABORT_INSTRUCTION;
    }

Here's a [manual] diff of 2.25 to current:

--- abort.c 2022-12-11 22:42:33.064470777 -0500
    /tmp/abort.c    2022-12-11 22:42:21.106728443 -0500
@@ -1,4  1,4 @@
-/* Copyright (C) 1991-2017 Free Software Foundation, Inc.
 /* Copyright (C) 1991-2022 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
@@ -13,7  13,7 @@

    You should have received a copy of the GNU Lesser General Public
    License along with the GNU C Library; if not, see
-   <http://www.gnu.org/licenses/>.  */
    <https://www.gnu.org/licenses/>.  */

 #include <libc-lock.h>
 #include <signal.h>
@@ -21,6  21,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <internal-signals.h>

 /* Try to get a machine dependent instruction which will make the
    program crash.  This is used in case everything else fails.  */
@@ -30,11  31,8 @@
 # define ABORT_INSTRUCTION
 #endif

-#include <libio/libioP.h>
-#define fflush(s) _IO_flush_all_lockp (0)
-
 /* Exported variable to locate abort message in core files etc.  */
-struct abort_msg_s *__abort_msg __attribute__ ((nocommon));
 struct abort_msg_s *__abort_msg;
 libc_hidden_def (__abort_msg)

 /* We must avoid to run in circles.  Therefore we remember how far we
@@ -50,32  48,24 @@
 abort (void)
 {
   struct sigaction act;
-  sigset_t sigs;

   /* First acquire the lock.  */
   __libc_lock_lock_recursive (lock);

   /* Now it's for sure we are alone.  But recursive calls are possible.  */

-  /* Unlock SIGABRT.  */
   /* Unblock SIGABRT.  */
   if (stage == 0)
     {
         stage;
-      if (__sigemptyset (&sigs) == 0 &&
-     __sigaddset (&sigs, SIGABRT) == 0)
-   __sigprocmask (SIG_UNBLOCK, &sigs, (sigset_t *) NULL);
-    }
-
-  /* Flush all streams.  We cannot close them now because the user
-     might have registered a handler for SIGABRT.  */
-  if (stage == 1)
-    {
-        stage;
-      fflush (NULL);
       internal_sigset_t sigs;
       internal_sigemptyset (&sigs);
       internal_sigaddset (&sigs, SIGABRT);
       internal_sigprocmask (SIG_UNBLOCK, &sigs, NULL);
     }

   /* Send signal which possibly calls a user handler.  */
-  if (stage == 2)
   if (stage == 1)
     {
       /* This stage is special: we must allow repeated calls of
     `abort' when a user defined handler for SIGABRT is installed.
@@ -93,7  83,7 @@
     }

   /* There was a handler installed.  Now remove it.  */
-  if (stage == 3)
   if (stage == 2)
     {
         stage;
       memset (&act, '\0', sizeof (struct sigaction));
@@ -103,30  93,22 @@
       __sigaction (SIGABRT, &act, NULL);
     }

-  /* Now close the streams which also flushes the output the user
-     defined handler might has produced.  */
-  if (stage == 4)
-    {
-        stage;
-      __fcloseall ();
-    }
-
   /* Try again.  */
-  if (stage == 5)
   if (stage == 3)
     {
         stage;
       raise (SIGABRT);
     }

   /* Now try to abort using the system specific command.  */
-  if (stage == 6)
   if (stage == 4)
     {
         stage;
       ABORT_INSTRUCTION;
     }

   /* If we can't signal ourselves and the abort instruction failed, exit.  */
-  if (stage == 7)
   if (stage == 5)
     {
         stage;
       _exit (127);
  • Related