|
| 1 | +# CWE-754: Improper Check for Unusual or Exceptional Conditions - Float |
| 2 | + |
| 3 | +Ensure to have handling for exceptional floating-point values. |
| 4 | + |
| 5 | +The float class has the capability to interpret various input values as floating-point numbers. Some special cases can interpret input values as |
| 6 | + |
| 7 | +* Positive Infinity |
| 8 | +* Negative Infinity |
| 9 | +* NaN (Not-a-Number) |
| 10 | + |
| 11 | +TThese floating-point class values represent numbers that fall outside the typical range and exhibit unique behaviors. NaN (Not a Number) lacks a defined order and is not considered equal to any value, including itself. Hence, evaluating an expression such as "NaN == NaN" returns "False." |
| 12 | + |
| 13 | +## Non-Compliant Code Example |
| 14 | + |
| 15 | +In the `noncompliant01.py` example, the code allows a user to add objects of a certain weight to a package—as long as the total weight remains below 100 units, the object is added. However, the checks in the code are inadequate for filtering out exceptional conditions. For example, converting the input to a float can mask invalid inputs (such as non-numeric strings), allowing erroneous or unintended values (e.g., NaN or infinity) to bypass validation. |
| 16 | + |
| 17 | +*[noncompliant01.py](noncompliant01.py):* |
| 18 | + |
| 19 | +```py |
| 20 | +# SPDX-FileCopyrightText: OpenSSF project contributors |
| 21 | +# SPDX-License-Identifier: MIT |
| 22 | +"""Non-compliant Code Example""" |
| 23 | + |
| 24 | +import sys |
| 25 | + |
| 26 | + |
| 27 | +class Package: |
| 28 | + """Class representing a package object""" |
| 29 | + |
| 30 | + def __init__(self): |
| 31 | + self.package_weight = float(0.0) |
| 32 | + self.max_package_weight = 100.0 |
| 33 | + |
| 34 | + def add_to_package(self, object_weight: str): |
| 35 | + """Function for adding an object into the package""" |
| 36 | + value = float(object_weight) |
| 37 | + # This is dead code as value gets type cast to float, |
| 38 | + # hence will never be equal to string "NaN" |
| 39 | + if value == "NaN": |
| 40 | + raise ValueError("'NaN' not a number") |
| 41 | + # This is also dead code as value is type cast to float, |
| 42 | + # unusual inputs like -infinity will not get caught |
| 43 | + if isinstance(value, float) is not True: |
| 44 | + raise ValueError("not a number") |
| 45 | + if self.package_weight + value > self.max_package_weight: |
| 46 | + raise ValueError("Addition would exceed maximum package weight.") |
| 47 | + |
| 48 | + self.package_weight += value |
| 49 | + |
| 50 | + def get_package_weight(self): |
| 51 | + """Getter for outputting the package's current weight""" |
| 52 | + return self.package_weight |
| 53 | + |
| 54 | + |
| 55 | +##################### |
| 56 | +# exploiting above code example |
| 57 | +##################### |
| 58 | +package = Package() |
| 59 | +print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n") |
| 60 | +for item in [100, "-infinity", sys.float_info.max, "NaN", -100]: |
| 61 | + print(f"package.add_to_package({item})") |
| 62 | + try: |
| 63 | + package.add_to_package(item) |
| 64 | + print( |
| 65 | + f"package.get_package_weight() = {package.get_package_weight():.2f}\n" |
| 66 | + ) |
| 67 | + |
| 68 | + except Exception as e: |
| 69 | + print(e) |
| 70 | +``` |
| 71 | + |
| 72 | +* Setting a value above sys.float_info.max does not increase the held value. In some cases, incrementing package_weight with a high enough value may turn its value into inf |
| 73 | +* Setting the added value to -infinity and +infinity causes the value of the package_weight to be infinite as well. |
| 74 | +* Setting the package_weight to NaN, which is not a valid value. |
| 75 | + |
| 76 | +**Example `noncompliant01.py` output:** |
| 77 | + |
| 78 | +```bash |
| 79 | +Original package's weight is 0.00 units |
| 80 | +
|
| 81 | +package.add_to_package(100) |
| 82 | +package.get_package_weight() = 100.00 |
| 83 | +
|
| 84 | +package.add_to_package(-infinity) |
| 85 | +package.get_package_weight() = -inf |
| 86 | +
|
| 87 | +package.add_to_package(1.7976931348623157e+308) |
| 88 | +package.get_package_weight() = -inf |
| 89 | +
|
| 90 | +package.add_to_package(NaN) |
| 91 | +package.get_package_weight() = nan |
| 92 | +
|
| 93 | +package.add_to_package(-100) |
| 94 | +package.get_package_weight() = nan |
| 95 | +``` |
| 96 | +
|
| 97 | +## Compliant Solution |
| 98 | +
|
| 99 | +Exceptional values and out-of-range values are handled in `compliant01.py`. Due to the nature of the code example, negative values are also checked for. |
| 100 | +The `isfinite` function from the `math` library is useful for checking for `NaN`, `infinity` and `-infinity` values. It checks if a value is neither infinite nor a NaN. |
| 101 | +
|
| 102 | +Other functions from the `math` library that could be of use are `isnan`, which checks if an inputted value is "NaN", and `isinf` (which checks if a value is positive or negative infinity). |
| 103 | +
|
| 104 | +*[compliant01.py](compliant01.py):* |
| 105 | +
|
| 106 | +```py |
| 107 | +# SPDX-FileCopyrightText: OpenSSF project contributors |
| 108 | +# SPDX-License-Identifier: MIT |
| 109 | +""" Compliant Code Example """ |
| 110 | +
|
| 111 | +
|
| 112 | +import sys |
| 113 | +from math import isfinite, isnan |
| 114 | +from typing import Union |
| 115 | +
|
| 116 | +
|
| 117 | +class Package: |
| 118 | + """Class representing a package object.""" |
| 119 | + def __init__(self) -> None: |
| 120 | + self.package_weight: float = 0.0 |
| 121 | + self.max_package_weight: float = 100.0 |
| 122 | +
|
| 123 | + def add_to_package(self, object_weight: Union[str, int, float]) -> None: |
| 124 | + """Add an object into the package after validating its weight.""" |
| 125 | + try: |
| 126 | + value = float(object_weight) |
| 127 | + except (ValueError, TypeError) as e: |
| 128 | + raise ValueError("Input cannot be converted to a float.") from e |
| 129 | + if isnan(value): |
| 130 | + raise ValueError("Input is not a number") |
| 131 | + if not isfinite(value): |
| 132 | + raise ValueError("Input is not a finite number.") |
| 133 | + if value < 0: |
| 134 | + raise ValueError("Weight must be a non-negative number.") |
| 135 | + if self.package_weight + value > self.max_package_weight: |
| 136 | + raise ValueError("Addition would exceed maximum package weight.") |
| 137 | + |
| 138 | + print(f"Adding an object that weighs {value} units to package") |
| 139 | + self.package_weight += value |
| 140 | + |
| 141 | + def get_package_weight(self) -> float: |
| 142 | + """Return the package's current weight.""" |
| 143 | + return self.package_weight |
| 144 | +
|
| 145 | +
|
| 146 | +##################### |
| 147 | +# exploiting above code example |
| 148 | +##################### |
| 149 | + |
| 150 | + |
| 151 | +package = Package() |
| 152 | +print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n") |
| 153 | +for item in [100, "-infinity", sys.float_info.max, "NaN", -100]: |
| 154 | + print(f"package.add_to_package({item})") |
| 155 | + try: |
| 156 | + package.add_to_package(item) |
| 157 | + print( |
| 158 | + f"package.get_package_weight() = {package.get_package_weight():.2f}\n" |
| 159 | + ) |
| 160 | + |
| 161 | + except Exception as e: |
| 162 | + print(e) |
| 163 | +``` |
| 164 | +
|
| 165 | +The compliant code example successfully ensures that any object added to the package is a valid and anticipated float value, and that the code cannot be exploited by inputting values such as NaN, infinite or -infinite. |
| 166 | +
|
| 167 | +**Example `compliant01.py` output:** |
| 168 | +
|
| 169 | +```bash |
| 170 | +Original package's weight is 0.00 units |
| 171 | + |
| 172 | +package.add_to_package(100) |
| 173 | +Adding an object that weighs 100.0 units to package |
| 174 | +package.get_package_weight() = 100.00 |
| 175 | + |
| 176 | +package.add_to_package(-infinity) |
| 177 | +Input is not a finite number. |
| 178 | +package.add_to_package(1.7976931348623157e+308) |
| 179 | +Addition would exceed maximum package weight. |
| 180 | +package.add_to_package(NaN) |
| 181 | +Input is not a number |
| 182 | +package.add_to_package(-100) |
| 183 | +Weight must be a non-negative number. |
| 184 | +``` |
| 185 | + |
| 186 | +## Automated Detection |
| 187 | + |
| 188 | +||||| |
| 189 | +|:---|:---|:---|:---| |
| 190 | +|Tool|Version|Checker|Description| |
| 191 | +|bandit|1.7.4|no detection|| |
| 192 | + |
| 193 | +## Related Guidelines |
| 194 | + |
| 195 | +||| |
| 196 | +|:---|:---| |
| 197 | +|[SEI CERT C Coding Standard](https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard)|[FLP04-C. Check floating-point inputs for exceptional values](https://wiki.sei.cmu.edu/confluence/display/c/FLP04-C.+Check+floating-point+inputs+for+exceptional+values)| |
| 198 | +|[SEI CERT C++ Coding Standard](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=88046682)|[VOID FLP04-CPP. Check floating point inputs for exceptional values](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=88046805)| |
| 199 | +|[SEI CERT Oracle Coding Standard for Java](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java?src=sidebar)|[NUM08-J. Check floating-point inputs for exceptional values](https://wiki.sei.cmu.edu/confluence/display/java/NUM08-J.+Check+floating-point+inputs+for+exceptional+values)| |
| 200 | +|[CWE MITRE Pillar](http://cwe.mitre.org/)|[CWE-703: Improper Check or Handling of Exceptional Conditions](https://cwe.mitre.org/data/definitions/703.html)| |
| 201 | +|[MITRE CWE Base](https://cwe.mitre.org/)|[CWE-754: Improper Check for Unusual or Exceptional Conditions](https://cwe.mitre.org/data/definitions/754.html)| |
| 202 | + |
| 203 | +## Biblography |
| 204 | + |
| 205 | +||| |
| 206 | +|:---|:---| |
| 207 | +|[NUM07-P3 Do not attempt comparisons with NaN](https://eteamspace.internal.ericsson.com/display/DEVEN/CWE-230%3A+Improper+Handling+of+Missing+Values)| NUM07-P3 Do not attempt comparisons with NaN.| |
0 commit comments