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

Compare with Current View Page History

« Previous Version 21 Next »

With one exception, accessing or modifying shared objects in signal handlers can lead to race conditions, opening up security holes.

According to the "Signals and Interrupts" section of the C99 Rationale:

The C89 Committee concluded that about the only thing a strictly conforming program can do in a signal handler is to assign a value to a volatile static variable which can be written uninterruptedly and promptly return.

The C99 standard dictates the use of volatile sig_atomic_t. The type of sig_atomic_t is implementation defined, although there are bounding constraints. Only assign integer values from 0 through 127 to a variable of type sig_atomic_t to be fully portable.

Non-Compliant Code Example

err_msg is updated to reflect the SIGINT signal that was encountered. Issues will occur if a SIGINT is generated prior to the malloc of err_msg finishing.

#include <signal.h> 
#include <stdlib.h>
#include <string.h>
 
char *err_msg; 
 
void handler(int signum) { 
  signal(signum, handler);
  strcpy(err_msg, "SIGINT encountered.");
} 
 
int main(void) { 
  signal(SIGINT, handler); 

  err_msg = malloc(24);
  if (err_msg == NULL) {
    /* handle error condition */
  }
  strcpy(err_msg, "No errors yet.");
 
  /* main code loop */

  return 0;
}

Compliant Solution

To be safe, signal handlers should only unconditionally set a flag of type volatile sig_atomic_t and return.

#include <signal.h> 
#include <stdlib.h>
#include <string.h>
 
char *err_msg; 
volatile sig_atomic_t e_flag = 0;
 
void handler(int signum) { 
  signal(signum, handler);
  e_flag = 1;
} 
 
int main(void) { 
  signal(SIGINT, handler); 

  err_msg = malloc(24);
  if (err_msg == NULL) {
    /* handle error condition */
  }

  strcpy(err_msg, "No errors yet.");
 
  /* main code loop */
  if (e_flag) {
    strcpy(err_msg, "SIGINT received.");
  }

  return 0;
}

The signal handler may also call the abort() function, the _Exit(), function, or the signal() function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. This may be necessary to ensure that the handler persists (see SIG01-A. Understand implementation-specific details regarding signal handler persistence).

Risk Assessment

Depending on the code, this could lead to any number of attacks, many of which could give root access. For an overview of some software vulnerabilities, see [[Zalewski 06]].

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

SIG31-C

3 (high)

3 (likely)

1 (high)

P9

L2

Related Vulnerabilities

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

Mitigation Strategies

Static Analysis

Compliance with this rule can be checked using structural static analysis checkers using the following algorithm:

  1. Traverse the abstract syntax tree (AST) to identify function calls to the signal function signal(int, void (*f)(int)).
  2. At each function call to signal(int, void (*f)(int)) get the second argument from the argument list. To make sure that this is not an overloaded function the function type signature is evaluated and/or the location of the declaration of the function is verified to be from the correct file (because this is not a link-time analysis it is not possible to test the library implementation). Any definition for signal() in the application is suspicious, because it should be in a library.
  3. Perform a nested query to identify all referenced objects with static storage duration. Verify that none of these objects are referenced as an rvalue, and that for each object referenced as an lvalue, the underlying type is sig_atomic_t.
  4. Report any violations detected.

References

[[Dowd 06]] Chapter 13, Synchronization and State
[[ISO/IEC 03]] "Signals and Interrupts"
[[Open Group 04]] longjmp
[OpenBSD] signal() Man Page
[Zalewski] http://lcamtuf.coredump.cx/signals.txt

  • No labels