You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 32 Next »

The signal() function has implementation-defined behavior and behaves differently, for example, on Windows than it does on Unix systems.

The following example code illustrates this behavior:

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

volatile sig_atomic_t e_flag = 0;

void handler(int signum) {
  e_flag = 1;
}

int main(void) {
  signal(SIGINT, handler);
  while (!e_flag) {}
  puts("Escaped from first while ()");
  e_flag = 0;
  while (!e_flag) {}
  puts("Escaped from second while ()");
  return 0;
}

Unix (and Unix-like) systems automatically reinstall signal handlers upon handler execution, meaning that the signal handler defined by the user is left in place until it is explicitly removed. For example, when this code is compiled with gcc 3.4.4 and executed under Red Hat Linux, the SIGINT is captured both times by handler.

% ./SIG01-A
^C
Escaped from first while ()
^C
Escaped from second while ()
%

When a signal handler is installed with the signal() function in Windows, the default action is restored for that signal after the signal is triggered. This means that signal handlers are not automatically reinstalled. For example, when this code is compiled with Microsoft Visual Studio 2005 version 8.0, only the first SIGINT is captured by handler.

> SIG01-A.exe
^C
Escaped from first while ()
^C
>

The second SIGINT executes the default action, which is to terminate program execution.

Different actions must be taken depending on whether or not the application requires signal handlers to be persistent.

Non-Persistent Handlers

Errors and potential vulnerabilities exist when the actual signal handler persistence behavior is inconsistent with the developer's expectations, for example, the developer expects the signal handler to persist but it does not. This is due to the possibility of asynchronous signals arising from outside the program, potentially from hostile sources.

Non-Compliant Code Example (Windows)

This non-compliant code example fails to persist the signal handler on Windows platforms.

void handler(int signum) {
  /* handling code */
}

Non-Compliant Code Example (Windows)

A common solution to make persistent signal handlers is to call signal() inside the handler itself, thus 'unresetting' the reset signal.

void handler(int signum) {
#ifdef WINDOWS
  signal(signum, handler);
#endif
  /* handling code */
}

Unfortunately this solution still permits a race window. starting when the OS first resets the signal, and ending when the handler calls signal(). During that time, a second signal sent to the program will still trigger the default signal behavior, thereby destroying persistence.

A secure solution would prevent the OS from resetting the signal in the first place, and thereby guarantee persistence. Unfortunately, Windows does not provide a secure solution to this problem.

Compliant Solution (POSIX)

POSIX defines the sigaction(2) function, which assigns handlers to signals like signal(2), but also allows one to explicitly set persistence. One can thus use sigaction(2) and sidestep the race window on non-persistent OS's.

/* Equivalent to signal( SIGUSR1, handler);
   but make signal persistent */
struct sigaction act;
act.sa_handler = &handler;
act.sa_flags = 0;
if (sigfillset( &act.sa_mask) != 0) {
  /* handle error */
}
if (sigaction(SIGUSR1, &act, NULL) != 0) {
  /* handle error */
}

In fact, POSIX recommends sigaction(2) and deprecates signal(2). Unfortunately, sigaction(2) is not C99-compliant.

Non-Persistent Handlers

Errors may also occur when the developer expects the default action to be restored for a signal, but instead, the signal handler persists.

Non-Compliant Code Example (Unix)

This non-compliant code example fails to reset the signal handler to its default behavior on Unix systems.

void handler(int signum) {
  /* handling code */
}

Compliant Solution (Unix)

A C99-compliant solution to reset the handler on a Unix system is to rebind the signal to the implementation-defined default handler in the first line of the handler itself.

void handler(int signum) {
#ifndef WINDOWS
  signal(signum, SIG_DFL);
#endif
  /* handling code */
}

Windows automatically resets handlers to default.

There is no race condition that can be utilizied by an attacker in sending a second signal here, because a second signal sent to the handler before it calls signal() will merely cause it to restart, and call signal() anyway.

Compliant Solution (POSIX)

POSIX defines the sigaction(2) function, which assigns handlers to signals like signal(2), but also allows one to explicitly set persistence. One can thus use sigaction(2) and sidestep the race window on non-persistent OS's.

/* Equivalent to signal( SIGUSR1, handler); */
   but make signal non-persistent */
struct sigaction act;
act.sa_handler = &handler;
act.sa_flags = SA_RESETHAND;
if (sigemptyset( &act.sa_mask) != 0) {
  /* handle error */
}
if (sigaction(SIGUSR1, &act, NULL) != 0) {
  /* handle error */
}

In fact, POSIX recommends sigaction(2) and deprecates signal(2). Unfortunately, sigaction(2) is not C99-compliant.

Risk Assessment

Failure to understand implementation-specific details regarding signal handler persistence can lead to unexpected behavior.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

SIG01-A

1 (low)

1 (unlikely)

3 (low)

P3

L3

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

References

[[ISO/IEC 9899-1999TR2]] Section 7.14.1.1, "The signal function"


SIG00-A. Mask signals handled by non-interruptible asynchronous signal handlers      12. Signals (SIG)       SIG02-A. Avoid using signals to implement normal functionality

  • No labels