Skip to content

Commit 6d5bf6c

Browse files
authored
Python Secure Coding Guide: CWE-230 (#967)
Signed-off-by: ewlxdnx <[email protected]>
1 parent a4567f2 commit 6d5bf6c

File tree

3 files changed

+175
-1
lines changed

3 files changed

+175
-1
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# CWE-230: Improper Handling of Missing Values
2+
3+
The `NaN` value should be stripped before as they can cause surprising or undefined behaviours in the statistics functions that sort or count occurrences [[2024 doc.python.org]](https://docs.python.org/3/library/statistics.html).
4+
In python, some datasets use `NaN` (not-a-number) to represent the missing data. This can be problematic as the `NaN` values are unordered. Any ordered comparison of a number to a not-a-number value are `False`. A counter-intuitive implication is that `not-a-number` values are not equal to themselves.
5+
6+
This behavior is compliant with IEEE 754[[2024 Wikipedia]](https://en.wikipedia.org/wiki/IEEE_754) a hardware induced compromise.
7+
The [example01.py](example01.py) code demonstrates various comparisons of `float('NaN')` all resulting in `False`.
8+
9+
```python
10+
# SPDX-FileCopyrightText: OpenSSF project contributors
11+
# SPDX-License-Identifier: MIT
12+
""" Code Example """
13+
14+
foo = float('NaN')
15+
print(f"foo={foo} type = {type(foo)}")
16+
17+
18+
print(foo == float("NaN") or
19+
foo is float("NaN") or
20+
foo < 3 or
21+
foo == foo or
22+
foo is None
23+
)
24+
25+
```
26+
27+
## Non-Compliant Code Example
28+
29+
This noncompliant code example [[2024 docs.python.org]](https://docs.python.org/3/reference/expressions.html#value-comparisons) attempts a direct comparison with `NaN` in `_value == float("NaN")`.
30+
*[noncompliant01.py](noncompliant01.py):*
31+
32+
```python
33+
# SPDX-FileCopyrightText: OpenSSF project contributors
34+
# SPDX-License-Identifier: MIT
35+
""" Non-compliant Code Example """
36+
37+
38+
def balance_is_positive(value: str) -> bool:
39+
"""Returns True if there is still enough value for a transaction"""
40+
_value = float(value)
41+
if _value == float("NaN") or _value is float("NaN") or _value is None:
42+
raise ValueError("Expected a float")
43+
if _value <= 0:
44+
return False
45+
else:
46+
return True
47+
48+
49+
#####################
50+
# attempting to exploit above code example
51+
#####################
52+
print(balance_is_positive("0.01"))
53+
print(balance_is_positive("0.001"))
54+
print(balance_is_positive("NaN"))
55+
56+
```
57+
58+
## Compliant Solution
59+
60+
In the `compliant01.py` code example, the method `Decimal.quantize` is used to gain control over known rounding errors in floating point values.
61+
62+
The decision by the `balance_is_positive` method is to `ROUND_DOWN` instead of the default `ROUND_HALF_EVEN`.
63+
64+
*[compliant01.py](compliant01.py):*
65+
66+
```python
67+
# SPDX-FileCopyrightText: OpenSSF project contributors
68+
# SPDX-License-Identifier: MIT
69+
""" Compliant Code Example """
70+
71+
from decimal import ROUND_DOWN, Decimal
72+
73+
74+
def balance_is_positive(value: str) -> bool:
75+
"""Returns True if there is still enough value for a transaction"""
76+
# TODO: additional input sanitation for expected type
77+
_value = Decimal(value)
78+
# TODO: exception handling
79+
return _value.quantize(Decimal(".01"), rounding=ROUND_DOWN) > Decimal("0.00")
80+
81+
82+
#####################
83+
# attempting to exploit above code example
84+
#####################
85+
print(balance_is_positive("0.01"))
86+
print(balance_is_positive("0.001"))
87+
print(balance_is_positive("NaN"))
88+
89+
```
90+
91+
`Decimal` throws a `decimal.InvalidOperation` for `NaN` values, the controlled rounding causes only `"0.01"` to return `True`.
92+
93+
In `compliant02.py` we use the `math.isnan` to verify if the value passed is a valid `float` value.
94+
95+
*[compliant02.py](compliant02.py):*
96+
97+
```python
98+
# SPDX-FileCopyrightText: OpenSSF project contributors
99+
# SPDX-License-Identifier: MIT
100+
""" Compliant Code Example """
101+
102+
import math
103+
104+
105+
def balance_is_positive(value: str) -> bool:
106+
"""Returns True if there is still enough value for a transaction"""
107+
_value = float(value)
108+
if math.isnan(_value) or _value is None:
109+
raise ValueError("Expected a float")
110+
if _value < 0.01:
111+
return False
112+
else:
113+
return True
114+
115+
116+
#####################
117+
# attempting to exploit above code example
118+
#####################
119+
print(balance_is_positive("0.01"))
120+
print(balance_is_positive("0.001"))
121+
print(balance_is_positive("NaN"))
122+
123+
```
124+
125+
The `balance_is_poitive` method will raise an `ValueError` for `NaN` values.
126+
127+
## Automated Detection
128+
129+
|Tool|Version|Checker|Description|
130+
|:----|:----|:----|:----|
131+
|Bandit|1.7.4 on Python 3.10.4|Not Available||
132+
|flake8|flake8-4.0.1 on python 3.10.4||Not Available|
133+
134+
## Related Guidelines
135+
136+
|||
137+
|:---|:---|
138+
|[SEI CERT Coding Standard for Java](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java)|[NUM07-J. Do not attempt comparisons with NaN](https://wiki.sei.cmu.edu/confluence/display/java/NUM07-J.+Do+not+attempt+comparisons+with+NaN)|
139+
|[ISO/IEC TR 24772:2013](https://wiki.sei.cmu.edu/confluence/display/java/Rule+AA.+References#RuleAA.References-ISO/IECTR24772-2013)|Injection RST|
140+
|[MITRE CWE Pillar](http://cwe.mitre.org/)|[CWE-703: Improper Check or Handling of Exceptional Conditions (mitre.org)](https://cwe.mitre.org/data/definitions/703.html)|
141+
|[MITRE CWE Pillar](http://cwe.mitre.org/)|[CWE-230: Improper Handling of Missing Values](https://cwe.mitre.org/data/definitions/230.html)|
142+
143+
## Bibliography
144+
145+
|||
146+
|:---|:---|
147+
|[[Python 3.10.4 docs]](https://docs.python.org/3/library/string.html#formatstrings)|Format String Syntax. Available from: <https://docs.python.org/3/library/string.html#formatstrings> \[Accessed 22 July 2025]|
148+
|[Python docs](https://docs.python.org/3/)|<https://docs.python.org/3/library/math.html#math.nan> \[Accessed 22 July 2025]|
149+
|[Python docs](https://docs.python.org/3/)|Python Value comparisons<https://docs.python.org/3/reference/expressions.html#value-comparisons> \[Accessed 22 July 2025]|
150+
|[[Wikipedia 2024]](https://realpython.com/python-string-formatting/)|IEEE 754: <https://en.wikipedia.org/wiki/IEEE_754> \[Accessed 22 July 2025]|

docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-230/compliant01.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# SPDX-FileCopyrightText: OpenSSF project contributors
22
# SPDX-License-Identifier: MIT
3-
""" Non-compliant Code Example """
3+
""" Compliant Code Example """
44

55
from decimal import ROUND_DOWN, Decimal
66

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# SPDX-FileCopyrightText: OpenSSF project contributors
2+
# SPDX-License-Identifier: MIT
3+
""" Compliant Code Example """
4+
5+
import math
6+
7+
8+
def balance_is_positive(value: str) -> bool:
9+
"""Returns True if there is still enough value for a transaction"""
10+
_value = float(value)
11+
if math.isnan(_value) or _value is None:
12+
raise ValueError("Expected a float")
13+
if _value < 0.01:
14+
return False
15+
else:
16+
return True
17+
18+
19+
#####################
20+
# attempting to exploit above code example
21+
#####################
22+
print(balance_is_positive("0.01"))
23+
print(balance_is_positive("0.001"))
24+
print(balance_is_positive("NaN"))

0 commit comments

Comments
 (0)