4

(Sorry for the long post but I wanted to be as accurate as possible)

I was trying to print the signal mask of the main thread while writing a C program, when I came across something strange regarding how the sigprocmask function works.

Background [Source: manual page sigprocmask(2)]
The sigprocmask function is used to fetch and/or change the signal mask of the calling thread.

/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • If oldset is non-NULL, the previous value of the signal mask is stored in oldset.
  • If set is NULL, then the signal mask is unchanged (i.e., how is ignored), but the current value of the signal mask is nevertheless returned in oldset (if it is not NULL).
  • A set of functions for modifying and inspecting variables of type sigset_t ("signal sets") is described in sigsetops(3). In example:
    • int sigemptyset(sigset_t *set);: initializes the signal set given by set to empty, with all signals excluded from the set.
    • int sigfillset(sigset_t *set);: initializes set to full, including all signals.
    • int sigismember(const sigset_t *set, int signum);: tests whether signum is a member of set.

Note: When creating a filled signal set, the glibc sigfillset function does not include the two real-time signals used internally by the NPTL threading implementation.

System specifics
Linux Distribution: Linux Mint 19.3 Cinnamon
Glibc version: 2.27 (default)
Also verified for Glibc version: 2.31.9

Output of uname -a:
Linux 5.0.0-32-generic #34~18.04.2-Ubuntu SMP Thu Oct 10 10:36:02 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Problem Replication

The program that alarmed me of something possibly going wrong is the following:

#define _GNU_SOURCE

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

#define BUFFER_SIZE 32

#define OUTOFBOUNDS
#undef  OUTOFBOUNDS

void print_set_bin(sigset_t *setp);

int main(void)
{
    sigset_t set;

    printf("NSIG = %d\n\n", NSIG);

    printf("Empty set:\n");
    if (sigemptyset(&set))  
    {
        perror("sigemptyset");
        return -1;
    }
    print_set_bin(&set);

    printf("Filled set:\n");
    if (sigfillset(&set))   
    {
        perror("sigfillset");
        return -1;
    }
    print_set_bin(&set);

    printf("After sigprocmask():\n");
    if (sigprocmask(SIG_BLOCK, NULL, &set))
    {
        perror("sigprocmask");
        return -1;
    }
    print_set_bin(&set); // Why non-empty?

    return 0;
}


void print_set_bin(sigset_t *setp)
{
    int sig, res;
    char buff[BUFFER_SIZE];

    if (!setp)
    {
        fprintf(stderr, "print_set_bin(): NULL parameter\n");
        return;
    }

#ifdef OUTOFBOUNDS
    for (sig = 0; sig <= NSIG; sig++)
#else
    for (sig = 1; sig < NSIG; sig++)
#endif
    {
        res = sigismember(setp, sig);
        if (res == -1)
        {
            snprintf(buff, BUFFER_SIZE, "sigisimember [%d]", sig);
            perror(buff);
        }
        else
            printf("%d", res);
    }
    printf(" [%s]\n\n", sigisemptyset(setp) ? "Empty" : "Non-empty");
}

Function print_set_bin prints the output of sigismember (0 for not a member, 1 for a member) for all signals. Macro definition NSIG (= 65) in signal.h is the biggest signal number plus one (1) as mentioned in /usr/include/x86_64-linux-gnu/bits/signum-generic.h. In the same file, it is also mentioned that this biggest signal number includes real-time signals (number range [32, 64]) and that signal number zero (0) is reserved for testing purposes.

As a result, my code posted above tests for signal numbers that belong in range [1, 64].

Below follows the output of the program:

NSIG = 65

Empty set:
0000000000000000000000000000000000000000000000000000000000000000 [Empty]

Filled set:
1111111111111111111111111111111001111111111111111111111111111111 [Non-empty]

After sigprocmask():
0000000000000000000000000000000000000000000000000000000000000000 [Non-empty]

Output explantion
In this program, variable set of type sigset_t is manipulated. At first, function sigemptyset is used to set all bits to zero, then function sigfillset is used to set all bits to one (except from two; see Note in Background section) and finally sigprocmask is used to store current signal mask to the same variable. After all operations taking place on the signal set, function print_set_bin is used to print which signals belong to the set and whether the set is empty (using sigisemptyset()).

The problem seems to be the last call to print_set_bin, where no signal is found belonging to the set, but the sigisemptyset function characterizes the set as non-empty. That got me thinking whether sigset_t holds more than 64 bits and at least one of them is non-zero.

Research
Tracing the header files included in signal.h, I found that sigset_t is a struct defined in /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h as __sigset_t.h and typedefed in /usr/include/x86_64-linux-gnu/bits/types/sigset_t.h:

#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
  unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;

At first I thought that 1024 bits were too many, but I then came across with this answer to another unix.stackexchange.com question. I then decided to use the implementation details of the sigset_t struct to print all the 1024 bits. I did it in the code following, by replacing function print_set_bin with function print_set_word that prints all _SIGSET_NWORDS (= 16) unsigned long ints allocated to __val array.

void print_set_word(sigset_t *setp)
{
    int i;

    if (!setp)
    {
        fprintf(stderr, "print_set_word(): NULL parameter\n");
        return;
    }

    for (i = 0; i < 16; i++)
    {
            printf("%lu\n", setp->__val[i]);
    }
    printf("[%s]\n\n", sigisemptyset(setp) ? "Empty" : "Non-empty");
}

Program output:

Empty set:
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
[Empty]

Filled set:
18446744067267100671
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
[Non-empty]

After sigprocmask():
0
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
18446744073709551615
[Non-empty]

Notes:

  • 18446744067267100671 == 0b1111111111111111111111111111111001111111111111111111111111111111 (64 bits set to 1 except from two; see Note in Background section)
  • 18446744073709551615 == 0b1111111111111111111111111111111111111111111111111111111111111111 (64 bits set to 1)

Output Explanation and Question
As you can see, sigemptyset() and sigfillset() manipulate all the 1024 bits of the set. Calling sigemptyset() instead of sigfillset() before calling sigprocmask() reveals thatsigprocmask() manipulates only the first 64 bits (one unsigned long int), leaving the remaining 1024-64=960 bits untouched! And here comes the long-waited question: Isn't this a bug? Shouldn't sigprocmask() write to the whole struct data?

3
  • A related question is unix.stackexchange.com/q/399342/5132 .
    – JdeBP
    Mar 10, 2020 at 15:43
  • @JdeBP If you read my question carefully, I refer the one you commented. The answer to that question verifies that sizeof(sigset_t) equals correctly to 128 Bytes. My question is about the sigprocmask function that doesn't manipulate all the 128 bytes in sigset_t. It is a whole different thing.
    – gzach
    Mar 10, 2020 at 16:07
  • It is, however, related, as I wrote.
    – JdeBP
    Mar 10, 2020 at 16:09

1 Answer 1

1

Yes, sigprocmask() was not working correctly!

On 11-Mar-2020 I filled a new bug report on the glibc bug tracker. A few minutes ago the bug status was changed to Resolved/Fixed as of glibc version 2.32.

Many thanks to the glibc developers involved in resolving this bug!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .