| 
 | 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 | +These 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 | +The `noncompliant01.py` intent is to ensure that adding objects will not exceed a total weight of `100` units. Validation fails as the code to test for exceptional conditions, such as `NaN` or `infinity`, is missing.  | 
 | 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: float = 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 False:  | 
 | 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 | +Some important considerations when dealing with floating-point values from `non-complaint01.py`.  | 
 | 73 | + | 
 | 74 | +* 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`.  | 
 | 75 | +* Setting the added value to `-infinity` and `+infinity` causes the value of the `package_weight` to be infinite as well.  | 
 | 76 | +* Adding `"NaN"`, which is not a valid value to `package_weight` will always return `"nan"`.  | 
 | 77 | + | 
 | 78 | +**Example `noncompliant01.py` output:**  | 
 | 79 | + | 
 | 80 | +```bash  | 
 | 81 | +Original package's weight is 0.00 units  | 
 | 82 | +
  | 
 | 83 | +package.add_to_package(100)  | 
 | 84 | +package.get_package_weight() = 100.00  | 
 | 85 | +
  | 
 | 86 | +package.add_to_package(-infinity)  | 
 | 87 | +package.get_package_weight() = -inf  | 
 | 88 | +
  | 
 | 89 | +package.add_to_package(1.7976931348623157e+308)  | 
 | 90 | +package.get_package_weight() = -inf  | 
 | 91 | +
  | 
 | 92 | +package.add_to_package(NaN)  | 
 | 93 | +package.get_package_weight() = nan  | 
 | 94 | +
  | 
 | 95 | +package.add_to_package(-100)  | 
 | 96 | +package.get_package_weight() = nan  | 
 | 97 | +```  | 
 | 98 | +
  | 
 | 99 | +## Compliant Solution  | 
 | 100 | +
  | 
 | 101 | +Exceptional values and out-of-range values are handled in `compliant01.py`. Some negative values are also checked for due to the nature of the code example.  | 
 | 102 | +The `isfinite` function from the `math` library is useful for checking for `NaN`, `infinity` and `-infinity` values. `math.isfinite` checks if a value is neither `infinite` nor a `NaN`.  | 
 | 103 | +
  | 
 | 104 | +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).  | 
 | 105 | +
  | 
 | 106 | +*[compliant01.py](compliant01.py):*  | 
 | 107 | +
  | 
 | 108 | +```py  | 
 | 109 | +# SPDX-FileCopyrightText: OpenSSF project contributors  | 
 | 110 | +# SPDX-License-Identifier: MIT  | 
 | 111 | +""" Compliant Code Example """  | 
 | 112 | +
  | 
 | 113 | +
  | 
 | 114 | +import sys  | 
 | 115 | +from math import isfinite, isnan  | 
 | 116 | +from typing import Union  | 
 | 117 | +
  | 
 | 118 | +
  | 
 | 119 | +class Package:  | 
 | 120 | +    """Class representing a package object."""  | 
 | 121 | +    def __init__(self) -> None:  | 
 | 122 | +        self.package_weight: float = 0.0  | 
 | 123 | +        self.max_package_weight: float = 100.0  | 
 | 124 | +
  | 
 | 125 | +    def add_to_package(self, object_weight: Union[str, int, float]) -> None:  | 
 | 126 | +        # TODO: input sanitation.  | 
 | 127 | +        # TODO: proper exception handling  | 
 | 128 | +        """Add an object into the package after validating its weight."""  | 
 | 129 | +        try:  | 
 | 130 | +            value = float(object_weight)  | 
 | 131 | +        except (ValueError, TypeError) as e:  | 
 | 132 | +            raise ValueError("Input cannot be converted to a float.") from e  | 
 | 133 | +        if isnan(value):  | 
 | 134 | +            raise ValueError("Input is not a number")  | 
 | 135 | +        if not isfinite(value):  | 
 | 136 | +            raise ValueError("Input is not a finite number.")  | 
 | 137 | +        if value < 0:  | 
 | 138 | +            raise ValueError("Weight must be a non-negative number.")  | 
 | 139 | +        if self.package_weight + value > self.max_package_weight:  | 
 | 140 | +            raise ValueError("Addition would exceed maximum package weight.")  | 
 | 141 | +   | 
 | 142 | +        print(f"Adding an object that weighs {value} units to package")  | 
 | 143 | +        self.package_weight += value  | 
 | 144 | +   | 
 | 145 | +    def get_package_weight(self) -> float:  | 
 | 146 | +        """Return the package's current weight."""  | 
 | 147 | +        return self.package_weight  | 
 | 148 | +
  | 
 | 149 | +
  | 
 | 150 | +#####################  | 
 | 151 | +# exploiting above code example  | 
 | 152 | +#####################  | 
 | 153 | +   | 
 | 154 | +   | 
 | 155 | +package = Package()  | 
 | 156 | +print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n")  | 
 | 157 | +for item in [100, "-infinity", sys.float_info.max, "NaN", -100]:  | 
 | 158 | +    print(f"package.add_to_package({item})")  | 
 | 159 | +    try:  | 
 | 160 | +        package.add_to_package(item)  | 
 | 161 | +        print(  | 
 | 162 | +            f"package.get_package_weight() = {package.get_package_weight():.2f}\n"  | 
 | 163 | +        )  | 
 | 164 | +   | 
 | 165 | +    except Exception as e:  | 
 | 166 | +        print(e)  | 
 | 167 | +```  | 
 | 168 | +
  | 
 | 169 | +This compliant code example will raise a `ValueError` for inputs that are `-infinity`, `infinity`, or `NaN`, with messages "Input is not a finite number" and "Input is not a number" respectively. It should also ensure weights are non-negative, returning "Weight must be a non-negative number" for negative inputs.  | 
 | 170 | +
  | 
 | 171 | +**Example `compliant01.py` output:**  | 
 | 172 | +
  | 
 | 173 | +```bash  | 
 | 174 | +Original package's weight is 0.00 units  | 
 | 175 | +   | 
 | 176 | +package.add_to_package(100)  | 
 | 177 | +Adding an object that weighs 100.0 units to package  | 
 | 178 | +package.get_package_weight() = 100.00  | 
 | 179 | +   | 
 | 180 | +package.add_to_package(-infinity)  | 
 | 181 | +Input is not a finite number.  | 
 | 182 | +package.add_to_package(1.7976931348623157e+308)  | 
 | 183 | +Addition would exceed maximum package weight.  | 
 | 184 | +package.add_to_package(NaN)  | 
 | 185 | +Input is not a number  | 
 | 186 | +package.add_to_package(-100)  | 
 | 187 | +Weight must be a non-negative number.  | 
 | 188 | +```  | 
 | 189 | + | 
 | 190 | +## Automated Detection  | 
 | 191 | + | 
 | 192 | +|||||  | 
 | 193 | +|:---|:---|:---|:---|  | 
 | 194 | +|Tool|Version|Checker|Description|  | 
 | 195 | +|bandit|1.7.4|no detection||  | 
 | 196 | + | 
 | 197 | +## Related Guidelines  | 
 | 198 | + | 
 | 199 | +|||  | 
 | 200 | +|:---|:---|  | 
 | 201 | +|[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)|  | 
 | 202 | +|[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)|  | 
 | 203 | +|[CWE MITRE Pillar](http://cwe.mitre.org/)|[CWE-703: Improper Check or Handling of Exceptional Conditions](https://cwe.mitre.org/data/definitions/703.html)|  | 
 | 204 | +|[MITRE CWE Base](https://cwe.mitre.org/)|[CWE-754: Improper Check for Unusual or Exceptional Conditions](https://cwe.mitre.org/data/definitions/754.html)|  | 
0 commit comments