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

Compare with Current View Page History

« Previous Version 34 Next »

Only call asynchronous-safe functions within signal handlers.

According to the "Signals and Interrupts" section of the C Rationale [[ISO/IEC 03]]:

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 could cause problems if the signal handler invokes a library function that was being executed at the time of the signal. Since library functions are not guaranteed to be reentrant, they should not be called from a signal handler that returns.

Similarly, Section 7.14.1 paragraph 5 of C99 [[ISO/IEC 9899-1999:TC2]] states that:

If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t, or the signal handler calls any function in the standard library other than 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.

Non-Compliant Code Example

In this non-compliant code example, main() invokes the malloc() function to allocated space to copy a string. The string literal is copied into the allocated memory, which is then printed and the memory freed. The program also registers the signal handler int_handler() to handle the terminal interrupt signal SIGINT.

Unfortunately, the free() function is not asynchronous-safe and its invocation from within a signal handler is a violation of this rule. If an interrupt signal is received during or after the free() call in main(), the heap may be corrupted.

#include <signal.h>

char *foo;

void int_handler() {
  free(foo);
  _Exit(0);
}

int main(void) {
  foo = malloc(sizeof("Hello World."));
  if (foo == NULL) {
    /* handle error condition */
  }
  signal(SIGINT, int_handler);
  strcpy(foo, "Hello World.");
  puts(foo);
  free(foo);
  return 0;
}

The _Exit() function called from within the int_handler() signal handler causes immediate program termination, and is async-safe, whereas exit() may call cleanup routines first, and is consequently not async-safe.

Implementation Details

OpenBSD

The OpenBSD signal() man page identifies functions that are asynchronous-signal safe. Applications may consequently invoke them, without restriction, from signal-catching functions.

POSIX

The following table from the the Open Group Base Specifications [[Open Group 04]] defines a set of functions that are either reentrant or non-interruptible by signals and are async-signal-safe. Applications may consequently invoke them, without restriction, from signal-catching functions:

_Exit()

_exit()

abort()

accept()

access()

aio_error()

aio_return()

aio_suspend()

alarm()

bind()

cfgetispeed()

cfgetospeed()

cfsetispeed()

cfsetospeed()

chdir()

chmod()

chown()

clock_gettime()

close()

connect()

creat()

dup()

dup2()

execle()

execve()

fchmod()

fchown()

fcntl()

fdatasync()

fork()

fpathconf()

fstat()

fsync()

ftruncate()

getegid()

geteuid()

getgid()

getgroups()

getpeername()

getpgrp()

getpid()

getppid()

getsockname()

getsockopt()

getuid()

kill()

link()

listen()

lseek()

lstat()

mkdir()

mkfifo()

open()

pathconf()

pause()

pipe()

poll()

posix_trace_event()

pselect()

raise()

read()

readlink()

recv()

recvfrom()

recvmsg()

rename()

rmdir()

select()

sem_post()

send()

sendmsg()

sendto()

setgid()

setpgid()

setsid()

setsockopt()

setuid()

shutdown()

sigaction()

sigaddset()

sigdelset()

sigemptyset()

sigfillset()

sigismember()

sleep()

signal()

sigpause()

sigpending()

sigprocmask()

sigqueue()

sigset()

sigsuspend()

sockatmark()

socket()

socketpair()

stat()

symlink()

sysconf()

tcdrain()

tcflow()

tcflush()

tcgetattr()

tcgetpgrp()

tcsendbreak()

tcsetattr()

tcsetpgrp()

time()

timer_getoverrun()

timer_gettime()

timer_settime()

times()

umask()

uname()

unlink()

utime()

wait()

waitpid()

write()

 

 

All functions not in the above table are considered to be unsafe with respect to signals. In the presence of signals, all functions defined by this volume of IEEE Std 1003.1-2001 shall behave as defined when called from or interrupted by a signal-catching function, with a single exception: when a signal interrupts an unsafe function and the signal-catching function calls an unsafe function, the behavior is undefined.

Compliant Solution

Signal handlers should be as concise as possible, ideally unconditionally setting a flag and returning. They may also call the _Exit() function.

#include <signal.h>

char *foo;

void int_handler() {
  _Exit(0);
}

int main(void) {
  foo = malloc(sizeof("Hello World."));
  if (foo == NULL) {
    /* handle error condition */
  }
  signal(SIGINT, int_handler);
  strcpy(foo, "Hello World.");
  puts(foo);
  free(foo);
  return 0;
}

Risk Assessment

Invoking functions that are not async-safe from within a signal handler may result in privilege escalation and other attacks. For an overview of some software vulnerabilities, see Zalewski's paper on understanding, exploiting and preventing signal-handling related vulnerabilities [[Zalewski 01]]. VU #834865 describes a vulnerability resulting from a violation of this rule.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

SIG30-C

3 (high)

3 (likely)

1 (high)

P9

L2

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

Mitigation Strategies

Static Analysis

Compliance with this rule and SIG31-C. Do not access or modify shared objects in signal handlers can be checked using structural static analysis checkers using the following algorithm:

  1. Assume an initial list of async-safe functions. This list would be specific to each OS, although POSIX does require a set of functions to be async-safe.
  2. All application defined functions that satisfy the async-safe property in that only call functions in the list of async-safe functions or modifies only sig_atomic_t variables are added to the async-safe function list. This handles the interprocedural case of calling a function in a signal handler that was itself an async-safe function.
  3. Traverse the abstract syntax tree (AST) to identify function calls to the signal function signal(int, void (*f)(int)).
  4. 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.
  5. Perform a nested query on the registered signal handler to get the list of functions that are called. Verify that each function being called is in the list of async-safe functions. To avoid repeatedly reviewing each function, the result of the first test of the function should be stored.
  6. Perform a nested query (can be combined with the one in the previous step) to identify all referenced objects. Verify that for each object referenced as an lvalue, that the type is sig_atomic_t or can be stripped away to a type for which sig_atomic_t is the base type (this handles the case of layers of typedef}}s hiding that a variable may really be of {{sig_atomic_t, but just not obviously so. The result from this analysis could be stored in the same location as the information from the previous step.
  7. If a violation is detected in either of the preceding two steps, report a violation (either a call to a non-asynch safe function or modification of a variable that was not sig_atomic_t).

References

[[Dowd 06]] Chapter 13, "Synchronization and State"
[[ISO/IEC 03]] Section 5.2.3, "Signals and interrupts"
[[ISO/IEC 9899-1999:TC2]] Section 7.14, "Signal handling <signal.h>"
[[Open Group 04]] longjmp
[OpenBSD] signal() Man Page
[[Zalewski 01]]

  • No labels