According to the C Standard, 7.14.1.1 paragraph 3 [ISO/IEC 9899:2024], if a signal handler returns when it has been entered as a result of a computational exception (that is, with the value of its argument of SIGFPE, SIGILL, SIGSEGV, or any other implementation-defined value corresponding to such an exception) returns, then the behavior is undefined. (See undefined behavior 130.)

When a signal occurs and func points to a function, it is implementation-defined whether the equivalent of signal (sig, SIG_DFL); is executed or the implementation prevents some implementation- defined set of signals (at least including sig) from occurring until the current signal handling has completed; in the case of SIGILL, the implementation may alternatively define that no action is taken. Then the equivalent of (*func)(sig); is executed. If and when the function returns, if the value of sig is SIGFPE, SIGILL, SIGSEGV, or any other implementation-defined value corresponding to a computational exception, the behavior is undefined; otherwise the program will resume execution at the point it was interrupted.

The Portable Operating System Interface (POSIX®), Base Specifications, Issue 7 [IEEE Std 1003.1:2013], adds SIGBUS to the list of computational exception signal handlers:

The behavior of a process is undefined after it returns normally from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or SIGSEGV signal that was not generated by kill(), sigqueue(), or raise().

Do not return from SIGFPE, SIGILL, SIGSEGV, or any other implementation-defined value corresponding to a computational exception, such as SIGBUS on POSIX systems, regardless of how the signal was generated.

Noncompliant Code Example

In this noncompliant code example, the division operation has undefined behavior if denom equals 0. (See INT33-C. Ensure that division and remainder operations do not result in divide-by-zero errors)  and may result in a SIGFPE signal to the program.)

#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>

volatile sig_atomic_t denom;

void sighandle(int s) {
  /* Fix the offending volatile */
  if (denom == 0) {
    denom = 1;
  }
}

int main(int argc, char *argv[]) {
  if (argc < 2) {
    return 0;
  }
 
  char *end = NULL;
  long temp = strtol(argv[1], &end, 10);
 
  if (end == argv[1] || 0 != *end ||
      ((LONG_MIN == temp || LONG_MAX == temp) && errno == ERANGE)) {
    /* Handle error */
  }
 
  denom = (sig_atomic_t)temp;
  signal(SIGFPE, sighandle);

  long result = 100 / (long)denom;
  return 0;
}

When compiled with some implementations, this noncompliant code example will loop infinitely if given the input 0. It illustrates that even when a SIGFPE handler attempts to fix the error condition while obeying all other rules of signal handling, the program still does not behave as expected.

Compliant Solution

The only portably safe way to leave a SIGFPE, SIGILL, or SIGSEGV handler is to invoke abort(), quick_exit(), or _Exit(). In the case of SIGFPE, the default action is abnormal termination, so no user-defined handler is required:

#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
  if (argc < 2) {
    return 0;
  }
 
  char *end = NULL;
  long denom = strtol(argv[1], &end, 10);
 
  if (end == argv[1] || 0 != *end ||
      ((LONG_MIN == denom || LONG_MAX == denom) && errno == ERANGE)) {
    /* Handle error */
  }
 
  long result = 100 / denom;
  return 0;
}

Implementation Details

Some implementations define useful behavior for programs that return from one or more of these signal handlers. For example, Solaris provides the sigfpe() function specifically to set a SIGFPE handler that a program may safely return from. Oracle also provides platform-specific computational exceptions for the SIGTRAP, SIGBUS, and SIGEMT signals. Finally, GNU libsigsegv takes advantage of the ability to return from a SIGSEGV handler to implement page-level memory management in user mode.

Risk Assessment

Returning from a computational exception signal handler is undefined behavior.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

SIG35-C

Low

Unlikely

High

P1

L3

Automated Detection

Tool

Version

Checker

Description

Axivion Bauhaus Suite

7.2.0

CertC-SIG35
CodeSonar
8.1p0

LANG.STRUCT.RFCESH

Return from Computational Exception Signal Handler

Cppcheck Premium
24.9.0

premium-cert-sig35-c

Fully implemented

Helix QAC

2024.3

DF4846, DF4847, DF4848


Klocwork
2024.3

CERT.STDLIB.SIGNAL


LDRA tool suite
9.7.1
44 SEnhanced enforcement
Parasoft C/C++test

2023.1

CERT_C-SIG35-aDo not return from a computational exception signal handler
PC-lint Plus

1.4

2671, 2764

Fully supported

Polyspace Bug Finder

R2024a

CERT C: Rule SIG35-CChecks for return from computational exception signal handler (rule fully covered)

Related Vulnerabilities

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

Bibliography

[IEEE Std 1003.1:2013]

2.4.1, Signal Generation and Delivery

[ISO/IEC 9899:2024]Subclause 7.14.1.1, "The signal Function"



7 Comments

    • I disagree with the Remediation Cost, as fixing code that handles these signals usually indicates a redesign is warranted.
  1. I agree with David's comments.

    I made some minor edits, including throwing the quote from C99 into the text.

    You completely ignored the part about "or any other implementation-defined value corresponding to a computational exception". I think you need to address this in some manner, possible with an example of what this might look like.

    Your noncompliant solution won't compile using Visual Studio or a C89 compiler because of where you have declared "result"; normally we try to have our examples compile under Visual Studio so people can test them out on that environment as well.

    The compliant solution should be exactly the same as the noncompliant code example, except that the specific issues addressed by the guideline should be repaired.

    In the sentence:

    "The noncompliant code example will loop infinitely on most systems when supplied with 0 as an argument."

    "on most systems" is a little too vague for me. Can you characterize these systems as to why this behavior is seen?

  2. It's worth noting that implementations sometimes define useful behavior for programs that return from one or more these signal handlers. For example, Solaris provides the sigfpe() function specifically to set a SIGFPE handler that a program may safely return from. GNU libsigsegv takes advantage of the ability to return from a SIGSEGV handler to implement page-level memory management in user mode.

    • The indentation in the code needs cleaned up. Please use the same indentation as found in other rules.
    • The CS needs some introductory text before the code. (You could simply fix this by swapping the text and code.)
    • BTW I thought SIGFPE typically calls abort() if no handler exists for it, so the CS is no different than not catching SIGFPE at all.
      • The text after the NCCE needs cleaned up.

      My only other comment is that it's worthwhile to mention some of Martin's comment in an Implementation-Details section.

  3. The purpose of the "Implementation Details" section is a bit unclear. It tells you that there are times when it's safe and useful to return from a computational exception signal handler, but it doesn't actually have an exception that lets you do so without being noncompliant.

    1. This is perfectly normal. Most of our rules are trying to help you avoid undefined behavior, as specified by the C standard. But many platforms actually define these behaviors. Relying on platform-specific behavior can make code safer, though non-portable. You can assume that every CERT rule has an exception that says "If you restrict yourself to a platform that defines this UB, then your code can rely on that platform's behavior without being insecure".