Skip to content

Commit ad663c7

Browse files
committed
CWE-754 - Improper Check for Unusual or Exceptional Conditions - Float
Signed-off-by: Bartlomiej Karas <[email protected]>
1 parent f8bd072 commit ad663c7

File tree

3 files changed

+266
-29
lines changed

3 files changed

+266
-29
lines changed
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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.|
Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,56 @@
11
# SPDX-FileCopyrightText: OpenSSF project contributors
22
# SPDX-License-Identifier: MIT
33
""" Compliant Code Example """
4+
5+
46
import sys
5-
from math import isinf, isnan
7+
from math import isfinite, isnan
8+
from typing import Union
69

710

811
class Package:
9-
def __init__(self):
10-
self.package_weight = float(1.0)
12+
"""Class representing a package object."""
13+
def __init__(self) -> None:
14+
self.package_weight: float = 0.0
15+
self.max_package_weight: float = 100.0
1116

12-
def put_in_the_package(self, user_input):
17+
def add_to_package(self, object_weight: Union[str, int, float]) -> None:
18+
"""Add an object into the package after validating its weight."""
1319
try:
14-
value = float(user_input)
15-
except ValueError:
16-
raise ValueError(f"{user_input} - Input is not a number!")
17-
18-
print(f"value is {value}")
19-
20-
if isnan(value) or isinf(value):
21-
raise ValueError(f"{user_input} - Input is not a real number!")
22-
20+
value = float(object_weight)
21+
except (ValueError, TypeError) as e:
22+
raise ValueError("Input cannot be converted to a float.") from e
23+
if isnan(value):
24+
raise ValueError("Input is not a number")
25+
if not isfinite(value):
26+
raise ValueError("Input is not a finite number.")
2327
if value < 0:
24-
raise ValueError(
25-
f"{user_input} - Packed object weight cannot be negative!"
26-
)
28+
raise ValueError("Weight must be a non-negative number.")
29+
if self.package_weight + value > self.max_package_weight:
30+
raise ValueError("Addition would exceed maximum package weight.")
2731

28-
if value >= sys.float_info.max - self.package_weight:
29-
raise ValueError(f"{user_input} - Input exceeds acceptable range!")
32+
print(f"Adding an object that weighs {value} units to package")
3033
self.package_weight += value
3134

32-
def get_package_weight(self):
35+
def get_package_weight(self) -> float:
36+
"""Return the package's current weight."""
3337
return self.package_weight
3438

3539

3640
#####################
3741
# exploiting above code example
3842
#####################
43+
44+
3945
package = Package()
4046
print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n")
41-
for item in [sys.float_info.max, "infinity", "-infinity", "nan"]:
47+
for item in [100, "-infinity", sys.float_info.max, "NaN", -100]:
48+
print(f"package.add_to_package({item})")
4249
try:
43-
package.put_in_the_package(item)
44-
print(f"Current package weight = {package.get_package_weight():.2f}\n")
50+
package.add_to_package(item)
51+
print(
52+
f"package.get_package_weight() = {package.get_package_weight():.2f}\n"
53+
)
54+
4555
except Exception as e:
4656
print(e)
Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
# SPDX-FileCopyrightText: OpenSSF project contributors
22
# SPDX-License-Identifier: MIT
3-
""" Non-compliant Code Example """
3+
"""Non-compliant Code Example"""
4+
45
import sys
56

67

78
class Package:
9+
"""Class representing a package object"""
10+
811
def __init__(self):
9-
self.package_weight = float(1.0)
12+
self.package_weight = float(0.0)
13+
self.max_package_weight = 100.0
1014

11-
def put_in_the_package(self, object_weight):
15+
def add_to_package(self, object_weight: str):
16+
"""Function for adding an object into the package"""
1217
value = float(object_weight)
13-
print(f"Adding an object that weighs {value} units")
18+
# This is dead code as value gets type cast to float,
19+
# hence will never be equal to string "NaN"
20+
if value == "NaN":
21+
raise ValueError("'NaN' not a number")
22+
# This is also dead code as value is type cast to float,
23+
# unusual inputs like -infinity will not get caught
24+
if isinstance(value, float) is not True:
25+
raise ValueError("not a number")
26+
if self.package_weight + value > self.max_package_weight:
27+
raise ValueError("Addition would exceed maximum package weight.")
28+
1429
self.package_weight += value
1530

1631
def get_package_weight(self):
32+
"""Getter for outputting the package's current weight"""
1733
return self.package_weight
1834

1935

@@ -22,9 +38,13 @@ def get_package_weight(self):
2238
#####################
2339
package = Package()
2440
print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n")
25-
for item in [sys.float_info.max, "infinity", "-infinity", "nan"]:
41+
for item in [100, "-infinity", sys.float_info.max, "NaN", -100]:
42+
print(f"package.add_to_package({item})")
2643
try:
27-
package.put_in_the_package(item)
28-
print(f"Current package weight = {package.get_package_weight():.2f}\n")
44+
package.add_to_package(item)
45+
print(
46+
f"package.get_package_weight() = {package.get_package_weight():.2f}\n"
47+
)
48+
2949
except Exception as e:
3050
print(e)

0 commit comments

Comments
 (0)