Bug 1951 - sigset() does not correctly return SIG_HOLD
Summary: sigset() does not correctly return SIG_HOLD
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: libc (show other bugs)
Version: 2.3.5
: P2 normal
Target Milestone: ---
Assignee: Ulrich Drepper
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-11-30 12:52 UTC by Michael Kerrisk
Modified: 2016-05-20 19:44 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Michael Kerrisk 2005-11-30 12:52:17 UTC
The logic of sigset() (sysdeps/posix/sigset.c) is tangled: when a signal
is blocked, it does not correctly return the previous disposition of the
signal in cases where the signal is currently blocked, or where the
disposition is being set to SIG_HOLD.

SUSv3 says the following:

    Upon successful completion, sigset() shall return SIG_HOLD if the
    signal had been blocked and the signal's previous disposition if
    it had not been blocked. Otherwise, SIG_ERR shall be returned and
    errno set to indicate the error.

In other words:

-- if the signal is currently blocked, then the return value of
   of a successful sigset() should always be SIG_HOLD, regardless
   of whether or not SIG_HOLD was specified as the new disposition.
   "TEST 1" below shows that glibc sigset() does not do this.

-- A successful sigset() call that specifies the new disposition
   as SIG_HOLD should return the previous disposition of the
   signal.  Instead (see "TEST 2" below), glibc sigset() always
   returns SIG_HOLD in this case.

I have included a test program at the foot of this mail.

When run on Linux/x86 glibc 2.3.5 (SUSE 10.0), we see the following:

    Linux tekapo 2.6.15-rc1-mm2 #6 SMP PREEMPT Wed Nov 23
    12:50:50 CET 2005 i686 i686 i386 GNU/Linux
    Wed Nov 30 13:38:12 CET 2005
    ===== TEST 1 =====
    Blocking signal with sighold()
    Signal mask after sighold()
                    2 (Interrupt)
    About to use sigset() to establish handler
    Previous disposition: SIG_DFL (should be SIG_HOLD)
    TEST FAILED!!!

    ===== TEST 2 =====
    About to use sigset() to set SIG_HOLD
    Previous disposition: SIG_HOLD (should be SIG_DFL)
    TEST FAILED!!!

    ===== TEST 3 =====
    About to use sigset() to set SIG_HOLD
    About to use sigset() to set SIG_HOLD (again)
    Previous disposition: SIG_HOLD (should be SIG_HOLD)

By contrast, here are the results on Solaris 8:

    SunOS sunbox 5.8 Generic_108528-27 sun4u sparc SUNW,Ultra-4
    Wed Nov 30 13:41:34 MET 2005
    ===== TEST 1 =====
    Blocking signal with sighold()
    Signal mask after sighold()
                    2 (Interrupt)
    About to use sigset() to establish handler
    Previous disposition: SIG_HOLD (should be SIG_HOLD)

    ===== TEST 2 =====
    About to use sigset() to set SIG_HOLD
    Previous disposition: SIG_DFL (should be SIG_DFL)

    ===== TEST 3 =====
    About to use sigset() to set SIG_HOLD
    About to use sigset() to set SIG_HOLD (again)
    Previous disposition: SIG_HOLD (should be SIG_HOLD)

And on HP-UX 11:

    HP-UX td192 B.11.11 U 9000/800 1839940656 unlimited-user license
    Wed Nov 30 07:42:00 EST 2005
    ===== TEST 1 =====
    Blocking signal with sighold()
    Signal mask after sighold()
                    2 (SIG-2)
    About to use sigset() to establish handler
    Previous disposition: SIG_HOLD (should be SIG_HOLD)

    ===== TEST 2 =====
    About to use sigset() to set SIG_HOLD
    Previous disposition: SIG_DFL (should be SIG_DFL)

    ===== TEST 3 =====
    About to use sigset() to set SIG_HOLD
    About to use sigset() to set SIG_HOLD (again)
    Previous disposition: SIG_HOLD (should be SIG_HOLD)

Cheers,

Michael

==========================

/* sigset_SIG_HOLD_bug.c */

#ifdef __sun
#define __EXTENSIONS__  /* To get NSIG defn */
#endif
#define _GNU_SOURCE
#define _XOPEN_SOURCE 500
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define TEST_SIG SIGINT

typedef void (*sighandler_t)(int);

#define errExit(msg)            do { perror(msg); exit(EXIT_FAILURE); \
                                } while (0)

#ifdef __hpux

/* A very minimal implementation of strsignal()... */

#define BUF_SIZE 100

char *
strsignal(int sig)
{
    static char buf[BUF_SIZE];

    snprintf(buf, BUF_SIZE, "SIG-%d", sig);
    return buf;
} /* strsignal */

#endif

static void     /* Print mask of blocked signals for this process */
printSigMask(const char *msg)
{
    sigset_t currMask;
    int sig, cnt;

    if (msg != NULL)
        printf("%s", msg);

    if (sigprocmask(SIG_BLOCK, NULL, &currMask) == -1)
        errExit("sigaction");

    cnt = 0;
    for (sig = 1; sig < NSIG; sig++) {
        if (sigismember(&currMask, sig)) {
            cnt++;
            printf("\t\t%d (%s)\n", sig, strsignal(sig));
        }
    }

    if (cnt == 0)
        printf("\t\t<empty signal set>\n");
} /* printSigMask */

static void
handler(int sig)
{
    printf("Caught signal %d\n", sig);
    printSigMask("Signal mask in handler\n");
    sleep(3);
    printf("Handler returning\n");
} /* handler */

static void
printDisposition(sighandler_t disp)
{
    if (disp == SIG_HOLD)
        printf("SIG_HOLD");
    else if (disp == SIG_DFL)
        printf("SIG_DFL");
    else if (disp == SIG_IGN)
        printf("SIG_IGN");
    else
        printf("handled at %lx", (long) disp);
} /* printDisposition */

static void
returnTest1(void)
{
    sighandler_t prev;

    printf("===== TEST 1 =====\n");
    printf("Blocking signal with sighold()\n");
    if (sighold(TEST_SIG) == -1)errExit("sighold");
    printSigMask("Signal mask after sighold()\n");

    printf("About to use sigset() to establish handler\n");
    prev = sigset(TEST_SIG, handler);
    if (prev == SIG_ERR) errExit("sigset");

    printf("Previous disposition: ");
    printDisposition(prev);
    printf(" (should be SIG_HOLD)\n");
    if (prev != SIG_HOLD)
        printf("TEST FAILED!!!\n");
} /* returnTest1 */

static void
returnTest2(void)
{
    sighandler_t prev;

    printf("\n===== TEST 2 =====\n");

    printf("About to use sigset() to set SIG_HOLD\n");
    prev = sigset(TEST_SIG, SIG_HOLD);
    if (prev == SIG_ERR) errExit("sigset");

    printf("Previous disposition: ");
    printDisposition(prev);
    printf(" (should be SIG_DFL)\n");
    if (prev != SIG_DFL)
        printf("TEST FAILED!!!\n");
} /* returnTest2 */

static void
returnTest3(void)
{
    sighandler_t prev;

    printf("\n===== TEST 3 =====\n");

    printf("About to use sigset() to set SIG_HOLD\n");
    prev = sigset(TEST_SIG, SIG_HOLD);
    if (prev == SIG_ERR) errExit("sigset");

    printf("About to use sigset() to set SIG_HOLD (again)\n");
    prev = sigset(TEST_SIG, SIG_HOLD);
    if (prev == SIG_ERR) errExit("sigset");

    printf("Previous disposition: ");
    printDisposition(prev);
    printf(" (should be SIG_HOLD)\n");
    if (prev != SIG_HOLD)
        printf("TEST FAILED!!!\n");
} /* returnTest3 */

int
main(int argc, char *argv[])
{
    pid_t childPid;

    system("uname -a; date\n");
    childPid = fork();
    if (childPid == -1) errExit("fork");

    if (childPid == 0) {
        returnTest1();
        exit(EXIT_SUCCESS);
    } else
        wait(NULL);

    childPid = fork();
    if (childPid == -1) errExit("fork");

    if (childPid == 0) {
        returnTest2();
        exit(EXIT_SUCCESS);
    } else
        wait(NULL);

    childPid = fork();
    if (childPid == -1) errExit("fork");

    if (childPid == 0) {
        returnTest3();
        exit(EXIT_SUCCESS);
    } else
        wait(NULL);

    exit(EXIT_SUCCESS);
} /* main */
Comment 1 Ulrich Drepper 2006-04-23 19:06:15 UTC
I've checked in a patch which adjusts the return value.  I also added the test case.

But the man page should say that this behavior is useful only (if ever) in
signal threaded code.  The handler is a process property and other threads might
as well change it.  The result if sigset cannot be used to reliably restore the
previous signal setting.
Comment 2 Michael Kerrisk 2006-04-23 22:43:45 UTC
Subject: Re:  sigset() does not correctly return SIG_HOLD

> I've checked in a patch which adjusts the return value.  I
> also added the test case.

Thanks.

> But the man page should say that this behavior is useful only (if ever) in
> signal threaded code.  The handler is a process property and other threads
> might as well change it.  The result if sigset cannot be used to reliably
> restore the previous signal setting.

Yes, the same comments hold for signal() and sigaction() too.  Maybe I 
will add something to signal(7) on this point.
Comment 3 Jackie Rosen 2014-02-16 18:25:16 UTC Comment hidden (spam)