Errors during floating point operations are often neglected by applications; instead the greatest effort is usually in validating the operands before an operation. Errors occurring during floating point operations are admittedly difficult to determine and diagnose, but the benefits of understanding how to check for these errors and employing the process of doing so may outweigh the costs. This recommendation suggests ways to capture errors during floating point operations.
Consider the following code:
int j = 0; int iResult = 1 / j;
Running the code above results in undefined behavior. On most implementations, integer division by zero is a terminal error, commonly printing a diagnostic message and aborting the program.
double x = 0.0; double dResult = 1 / x;
Though the code above also results in undefined behavior, most implementations do not view floating point division by zero as a terminal error. If extra steps are not taken the operation happens "silently."
The most portable way of determining a floating point exceptional condition has occurred is to make use of the floating point exception faculties provided by C99 in fenv.h
[[ISO/IEC 9899-1999]].
However, the C99 floating point exception functions are not without problems. The following caveats exist regarding the interaction between floating point exceptions and conversions:
- Conversion from floating point to integer may cause an "invalid" floating point exception. If this occurs, the value of that integer is undefined and should not be used.
- Most implementations fail to raise "invalid" for conversions from any negative or "large" positive FP values to unsigned integer types or to signed char (see tflt2int.c).
- When a non-integer floating point value is converted into an integer the "inexact" floating point exception is raised.
For information regarding floating point number conversions, see FLP34-C. Ensure that floating point conversions are within range of the new type.
Though the notion of floating point exceptions guaranteed by C99, floating point exceptions generally only exist on architectures utilizing IEEE-754. Despite this, these functions are the only somewhat portable solution to handling floating point exceptions.
A less portable, but sometimes more intuitive and/or more informative, solution is to make use of the faculties provided by the underlying implementation. If this approach is taken, the caveats of that system need to be well understood. The following table can serve as a starting point for some common architectures:
Operating System |
How to handle floating point errors |
|||
---|---|---|---|---|
Linux |
Use the C99 floating point exception functions. |
|||
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="5ed6bc02-9545-46a6-b8cc-ffb36f6febb8"><ac:plain-text-body><![CDATA[ |
Windows |
Either use the C99 floating point exception function or structured exception handling through |
AA. C References#MSDN]] |
]]></ac:plain-text-body></ac:structured-macro> |
Non-Compliant Code Example
In the following non-compliant code, floating point operations are carried out and there is no observation for errors during floating point operations. Please note that the range check on various operands for the operations has been intentionally ignored because we intend to capture the errors during a floating point operation.
fpOper_noErrorChecking(void) { /* ... */ double a = 1e-40, b, c = 0.1; float x = 0, y; /* inexact and underflows */ y = a; /* divide by zero operation */ b = y / x; /* inexact (loss of precision) */ c = sin(30) * a; /* ... */ }
However, as the comments indicate, various exceptional conditions occur, that may lead to unexpected arithmetic results.
Compliant Solution 1
This compliant solution uses C99 standard functions to handle floating point errors.
#include <fenv.h> fpOper_fenv(void) { double a = 1e-40, b, c = 0.1; float x = 0, y; int fpeRaised; /* ... */ feclearexcept(FE_ALL_EXCEPT); /* Store a into y is inexact and underflows: */ y = a; fpeRaised = fetestexcept(FE_ALL_EXCEPT); /* fpeRaised has FE_INEXACT and FE_UNDERFLOW */ feclearexcept(FE_ALL_EXCEPT); /* divide by zero operation */ b = y / x; fpeRaised = fetestexcept(FE_ALL_EXCEPT); /* fpeRaised has FE_DIVBYZERO */ feclearexcept(FE_ALL_EXCEPT); c = sin(30) * a; fpeRaised = fetestexcept(FE_ALL_EXCEPT); /* fpeRaised has FE_INEXACT */ feclearexcept(FE_ALL_EXCEPT); /* ... */ }
Compliant Solution 2 (Windows)
MS Visual Studio 2008 and earlier does not support C99 functions to handle floating point errors. Windows provides an alternative method to get floating point exception code using _statusfp()
, _statusfp2()
, and _clearfp()
.
fpOper_usingStatus(void) { /* ... */ double a = 1e-40, b, c; float x = 0, y; unsigned int rv = _clearfp(); /* Store into y is inexact and underflows: */ y = a; rv = _clearfp(); /* rv has _SW_INEXACT and _SW_UNDERFLOW */ /* zero-divide */ b = y / x; rv = _clearfp(); /* rv has _SW_ZERODIVIDE */ /* inexact */ c = sin(30) * a; rv = _clearfp(); /* rv has _SW_INEXACT */ /* ... */ }
Compliant Solution 3 (Windows)
MS Visual Studio 2008 also uses structured exception handling (SEH) to handle floating point operation. Using the SEH allows the programmer to change the results of the floating point operation that caused the error condition. Using SEH also provides more information about the error condition.
fp_usingSEH(void) { /* ... */ double a = 1e-40, b, c = 0.1; float x = 0, y; unsigned int rv ; unmask_fp(); _try { /* Store into y is inexact and underflows */ y = a; /* divide by zero operation */ b = y / x; /* inexact */ c = sin(30) * a; } _except (_fpieee_flt ( GetExceptionCode(), GetExceptionInformation(), fpieee_handler)) { printf ("fpieee_handler: EXCEPTION_EXECUTE_HANDLER"); } /* ... */ } void unmask_fpsr(void) { unsigned int u; unsigned int control_word; _controlfp_s(&control_word, 0, 0); u = control_word & ~(_EM_INVALID | _EM_DENORMAL | _EM_ZERODIVIDE | _EM_OVERFLOW | _EM_UNDERFLOW | _EM_INEXACT); _controlfp_s( &control_word, u, _MCW_EM); return ; } int fpieee_handler(_FPIEEE_RECORD *ieee) { /* ... */ switch (ieee->RoundingMode) { case _FpRoundNearest: /* ... */ break; /* Other RMs include _FpRoundMinusInfinity, * _FpRoundPlusInfinity, _FpRoundChopped */ /* ... */ } switch (ieee->Precision) { case _FpPrecision24: /* ... */ break; /* Other Ps include _FpPrecision53*/ /* ... */ } switch (ieee->Operation) { case _FpCodeAdd: /* ... */ break; /* Other Ops include _FpCodeSubtract, _FpCodeMultiply, * _FpCodeDivide, _FpCodeSquareRoot, _FpCodeCompare, * _FpCodeConvert, _FpCodeConvertTrunc */ /* ... */ } /* process the bitmap ieee->Cause */ /* process the bitmap ieee->Enable */ /* process the bitmap ieee->Status */ /* process the Operand ieee->Operand1, * evaluate format and Value */ /* process the Operand ieee->Operand2, *evaluate format and Value */ /* process the Result ieee->Result, * evaluate format and Value */ /* The result should be set according to the operation * specified in ieee->Cause and the result format as * specified in ieee->Result */ /* ... */ }
Risk Assessment
If floating point exceptions go undetected, they will cause one or more of these conditions: a security vulnerability, lower program efficiency, and inaccurate results. Most processors stall for a significant duration (sometimes up to a second or even more on 32-bit desktop processors) when an operation incurs a NaN.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
FLP03-A |
low |
probable |
medium |
P4 |
L3 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
[[IEEE 754]]
[[Intel 01]]
[[Keil 08]]
[[MSDN]] "fpieee_flt (CRT)"
[[Open Group 04]] "fenv.h - Floating point environment"
[[SecurityFocus 07]]
FLP02-A. Consider avoiding floating point numbers when precise computation is needed 05. Floating Point (FLP) FLP30-C. Do not use floating point variables as loop counters