Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Call only asynchronous-safe functions within signal handlers. For strictly conforming programs, only the C standard library functions abort(), _Exit(), and signal() can be called from within a signal handler. 

Subclause 7.14.1.1, paragraph 5, of the C Standard [ISO/IEC 9899:2011] , states that if the signal occurs other than as the result of calling the abort() or raise() function, the behavior is undefined if

...

Many systems define an implementation-specific list of asynchronous-safe functions. These functions can also be called within a signal handler. This restriction applies to library functions as well as application-defined functions.

According to Section Subclause 7.14.1.1 of the C Rationale [ISO/IEC C99 Rationale 2003],

When a signal occurs, the normal flow of control of a program is interrupted. If a signal occurs that is being trapped by a signal handler, that handler is invoked. When it is finished, execution continues at the point at which the signal occurred. This arrangement can cause problems if the signal handler invokes a library function that was being executed at the time of the signal.

...

Code Block
bgColor#FFcccc
langc
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

enum { MAXLINE = 1024 };
char *info = NULL;

void log_message(void) {
  fprintf(stderr, info);
}

void handler(int signum) {
  log_message();
  free(info);
  info = NULL;
}

int main(void) {
  if (signal(SIGINT, handler) == SIG_ERR) {
    /* Handle error */
  }
  info = (char *)malloc(MAXLINE);
  if (info == NULL) {
    /* Handle Error */
  }

  while (1) {
    /* Main loop program code. */

    log_message();

    /* More program code. */
  }
  return 0;
}

Compliant Solution

...

This noncompliant code example is similar to a vulnerability in an old version of Sendmail [VU #834865]. The intent is to execute code in a main() loop, which also logs some data. Upon receiving a SIGINT, the program transfers out of the loop, logs the error, and terminates.

However, an attacker can exploit this noncompliant code example by generating a SIGINT just before the second if statement in log_message(). The result is that longjmp() transfers control back to main(), where log_message() is called again. However, the first if statement would not be executed this time (because buf is not set to NULL as a result of the interrupt), and the program would write to the invalid memory location referenced by buf0.

Code Block
bgColor#ffcccc
langc
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>

enum { MAXLINE = 1024 };
static jmp_buf env;

void handler(int signum) {
  longjmp(env, 1);
}

void log_message(char *info1, char *info2) {
  static char *buf = NULL;
  static size_t bufsize;
  char buf0[MAXLINE];

  if (buf == NULL) {
    buf = buf0;
    bufsize = sizeof(buf0);
  }

  /*
   * Try to fit a message into buf, else reallocate
    * it on the heap and then log the message.
   */

  /*** VULNERABILITY IF SIGINT RAISED HERE ***/

  if (buf == buf0) {
    buf = NULL;
  }
}

int main(void) {
  if (signal(SIGINT, handler) == SIG_ERR) {
    /* Handle error */
  }
  char *info1;
  char *info2;

  /* info1 and info2 are set by user input here. */

  if (setjmp(env) == 0) {
    while (1) {
      /* Main loop program code. */
      log_message(info1, info2);
      /* More program code */
    }
  } else {
    log_message(info1, info2);
  }

  return 0;
}

...

Code Block
bgColor#ccccff
langc
#include <signal.h>
#include <stdlib.h>

enum { MAXLINE = 1024 };
volatile sig_atomic_t eflag = 0;

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

void log_message(char *info1, char *info2) {
  static char *buf = NULL;
  static size_t bufsize;
  char buf0[MAXLINE];

  if (buf == NULL) {
    buf = buf0;
    bufsize = sizeof(buf0);
  }

  /*
   * Try to fit a message into buf, else re-allocatereallocate
   *  it on the heap and then log the message.
   */
  if (buf == buf0) {
    buf = NULL;
  }
}

int main(void) {
  if (signal(SIGINT, handler) == SIG_ERR) {
    /* Handle error */
  }
  char *info1;
  char *info2;

  /* info1 and info2 are set by user input here. */

  while (!eflag) {
    /* Main loop program code. */
    log_message(info1, info2);
    /* More program code. */
  }

  log_message(info1, info2);

  return 0;
}

...

Code Block
bgColor#ffcccc
langc
#include <signal.h>
#include <stdlib.h>
 
void term_handler(int signum) {
  /* SIGTERM handling specific. */
}
 
void int_handler(int signum) {
  /* SIGINT handling specific. */
  if (raise(SIGTERM) != 0) {
    /* Handle error */
  }
}
 
int main(void) {
  if (signal(SIGTERM, term_handler) == SIG_ERR) {
    /* Handle error */
  }
  if (signal(SIGINT, int_handler) == SIG_ERR) {
    /* Handle error */
  }
 
  /* Program code */
  if (raise(SIGINT) != 0) {
    /* Handle error */
  }
  /* More code */
 
  return EXIT_SUCCESS;
}

...

In this compliant solution, the call to the raise() function inside handler() has been is replaced by a direct call to log_msg():

Code Block
bgColor#ccccff
langc
#include <signal.h>

void log_msg(int signum) {
  /* Log error message in some asynchronous-safe manner. */
}

void handler(int signum) {
  /* Do some handling specific to SIGINT. */
  log_msg(SIGUSR1);
}

int main(void) {
  if (signal(SIGUSR1, log_msg) == SIG_ERR) {
    /* Handle error */
  }
  if (signal(SIGINT, handler) == SIG_ERR) {
    /* Handle error */
  }

  /* programProgram code */
  if (raise(SIGINT) != 0) {
    /* Handle error */
  }
  /* More code */

  return 0;
}

Noncompliant Code Example (POSIX)

The POSIX standard [Open Group 2004] is contradictory regarding raise() in signal handlers. The POSIX standard [Open Group 2004] It prohibits signal handlers installed using signal() from calling the raise() function if the signal occurs as the result of calling the raise()kill()pthread_kill(), or sigqueue() functions. However, it allows the raise() function to be safely called within any signal handler. Consequently, it is not clear whether it is safe for POSIX applications to call raise() in signal handlers installed using signal(), but it is safe to call raise() in signal handlers installed using sigaction()

...

Code Block
bgColor#ffcccc
langc
#include <signal.h>

void log_msg(int signum) {
  /* Log error message. */
}

void handler(int signum) {
  /* Do some handling specific to SIGINT. */
  if (raise(SIGUSR1) != 0) {
    /* Handle error */
  }
}

int main(void) {
  signal(SIGUSR1, log_msg);
  signal(SIGINT, handler);
   
  /* programProgram code */
  if (raise(SIGINT) != 0) {
    /* Handle error */
  }
  /* More code */

  return 0;
}

...

The following table from the Open Group Base Specifications [Open Group 2004] defines a set of functions that are asynchronous-signal-safe. Applications may invoke these functions, without restriction, from a signal handler.

...

Subclause 7.14.1.1, paragraph 4, of the C Standard [ISO/IEC 9899:2011] states:

  If the signal occurs as the result of calling the abort or raise function, the signal handler shall not call the raise function.

...

Code Block
bgColor#ccccff
langc
#include <signal.h>

void log_msg(int signum) {
  /* Log error message in some asynchronous-safe manner. */
}

void handler(int signum) {
  /* Do some handling specific to SIGINT. */
  if (raise(SIGUSR1) != 0) {
    /* Handle error */
  }
}

int main(void) {
  struct sigaction act;
  act.sa_flags = 0;
  if (sigemptyset(&act.sa_mask) != 0) {
    /* Handle error */
  }
  act.sa_handler = log_msg;
  if (sigaction(SIGUSR1, &act, NULL) != 0) {
    /* Handle error */
  }
  act.sa_handler = handler;
  if (sigaction(SIGINT, &act, NULL) != 0) {
    /* Handle error */
  }

  /* programProgram code */
  if (raise(SIGINT) != 0) {
    /* Handle error */
  }
  /* More code */

  return 0;
}

...

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

SIG30-C

highHigh

likelyLikely

mediumMedium

P18

L1

Automated Detection

Tool

Version

Checker

Description

Compass/ROSE  Can detect violations of the rule for single-file programs

LDRA tool suite

Include Page
LDRA_V
LDRA_V

88 D
89 D 

Fully implemented

Splint

Include Page
Splint_V
Splint_V

 

 

...

For an overview of software vulnerabilities resulting from improper signal handling, see Zalewski's paper [Zalewski 2001] on understanding, exploiting, and preventing signal-handling-related vulnerabilities [Zalewski 2001]. VU #834865 describes a vulnerability resulting from a violation of this rule.

Another notable case where using the longjmp() function in a signal handler caused a serious vulnerability is wu-ftpd 2.4 [Greenman 1997]. The effective user ID is set to 0 in one signal handler. If a second signal interrupts the first, a call is made to longjmp(), returning the program to the main thread but without lowering the user's privileges. These escalated privileges can be used for further exploitation.

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

...

CERT C++ Secure Coding StandardSIG30-CPP. Call only asynchronous-safe functions within signal handlers
ISO/IEC TR 17961 (Draft)Calling functions in the C Standard Library other than abort, _Exit, and signal from within a signal handler [asyncsig]
MITRE CWECWE-479, Unsafe function call from a signal handler

Bibliography

[Dowd 2006]Chapter 13, "Synchronization and State"[ISO/IEC C99 Rationale 2003]Section Subclause 5.2.3, "Signals and Interrupts"
Section Subclause 7.14.1.1, "The signal Function"
[Dowd 2006]Chapter 13, "Synchronization and State"
[ISO/IEC 9899:2011]Section Subclause 7.14.1.1, "The signal function"
[Open Group 2004]longjmp()
[OpenBSD]signal() Man Page
[Zalewski 2001]"Delivering Signals for Fun and Profit"

...