Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# CWE-197: Numeric Truncation Error

Ensure to have predictable outcomes in loops by using int instead of `float` variables as a counter.

Floating-point arithmetic can only represent a finite subset of real numbers [[IEEE Std 754-2019](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8766229)], such as `0.555....` represented by `0.5555555555555556` also discussed in [CWE-1339: Insufficient Precision or Accuracy of a Real Number](https://github.com/ossf/wg-best-practices-os-developers/tree/main/docs/Secure-Coding-Guide-for-Python/CWE-682/CWE-1339). Code examples in this rule are based on [Albing and Vossen, 2017].

Side effects of using `float` as a counter is demonstrated in `example01.py` showcasing that calculating `0.1 + 0.2` does not end up as `0.3`.

[*example01.py:*](example01.py)

```py
""" Code Example """

value = 0.0
while value <= 1:
print(f"{type(value)} {value}")
value += 0.1
```

**Output of exampl01.py:**

```bash
<class 'float'> 0.0
<class 'float'> 0.1
<class 'float'> 0.2
<class 'float'> 0.30000000000000004
<class 'float'> 0.4
<class 'float'> 0.5
<class 'float'> 0.6
<class 'float'> 0.7
<class 'float'> 0.7999999999999999
<class 'float'> 0.8999999999999999
<class 'float'> 0.9999999999999999
```

## Non-Compliant Code Example

The `noncompliant01.py` code demonstrates a side effect when a floating point counter is used.

[*noncompliant01.py:*](noncompliant01.py)

```py
""" Non-compliant Code Example """
counter = 0.0
while counter <= 1.0:
if counter == 0.8:
print("we reached 0.8")
break # never going to reach this
counter += 0.1
```

The `noncompliant01.py` code will never print "we are at 0.8" due to lack of precision or controlled rounding.

## Compliant Solution

The `compliant01.py` makes use of integer as long as possible and only converts to `float` where needed.

[*compliant01.py:*](compliant01.py)

```py
""" Compliant Code Example """
counter = 0
while counter <= 10:
value = counter/10
if value == 0.8:
print("we reached 0.8")
break
counter += 1
```

## Non-Compliant Code Example

The `example02.py` code demonstrates more precision limites in floating numbers.

[*example02.py:*](example02.py)

```py
""" Code Example """
print(f"{1.0 + 1e-16:.20f}")
print(f"{1.0 + 1e-15:.20f}")
```

**Output of example02.py:**

```bash
1.00000000000000000000
1.00000000000000111022
```

Below `noncompliant02.py` code tries to increment a floating-point `COUNTER` by a too small value causing an infinite loop.

[*noncompliant02.py:*](noncompliant02.py)

```py
""" Non-compliant Code Example """
counter = 1.0 + 1e-16
target = 1.0 + 1e-15
while counter <= target: # never ends
print(f"counter={counter / 10**16 :.20f}")
print(f" target={target / 10**16:.20f}")
counter += 1e-16

```

The code will loop forever due to missing precision in the initial calculation of `COUNTER = 1.0 + 1e-16`.

## Compliant Solution

Use of an `int` loop counter that is only converted to `float` when required is demonstrated in `compliant2.py`.

[*compliant02.py:*](compliant02.py)

```py
""" Compliant Code Example """
counter = 1
target = 10
while counter <= target:
print(f"counter={counter / 10**16 :.20f}")
print(f" target={target / 10**16:.20f}")
counter += 1
```

## Defnitions

|Definition|Explanation|Reference|
|:---|:---|:---|
|Loop Counters|loop counters are variables used to control the iterations of a loop|[Loop counter - Wikipedia](https://en.wikipedia.org/wiki/For_loop#Loop_counters)|

## Automated Detection

|Tool|Version|Checker|Description|
|:---|:---|:---|:---|
|Bandit|1.7.4 on Python 3.10.4|Not Available||
|Flake8|8-4.0.1 on Python 3.10.4|Not Available||

## Related Guidelines

|||
|:---|:---|
|[MITRE CWE](http://cwe.mitre.org/)|Pillar [CWE-664: Improper Control of a Resource Through its Lifetime (4.13) (mitre.org)](https://cwe.mitre.org/data/definitions/664.html)|
|[MITRE CWE](http://cwe.mitre.org/)|Class [CWE-197: Numeric Truncation Error](https://cwe.mitre.org/data/definitions/197.html)|
|[SEI CERT Coding Standard for Java](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java)|[NUM09-J. Do not use floating-point variables as loop counters](https://wiki.sei.cmu.edu/confluence/display/java/NUM09-J.+Do+not+use+floating-point+variables+as+loop+counters)|
|[SEI CERT C Coding Standard](https://web.archive.org/web/20220511061752/https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard)|[FLP30-C. Do not use floating-point variables as loop counters](https://web.archive.org/web/20220511061752/https://wiki.sei.cmu.edu/confluence/display/c/FLP30-C.+Do+not+use+floating-point+variables+as+loop+counters)|
|[ISO/IEC TR 24772:2010]||

## Biblography

|||
|:---|:---|
|[IEEE Std 754-2019](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8766229)|IEEE Standard for Floating-Point Arithmetic, available from: [https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8766229](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8766229), [Last accessed June 2024] |
|[Wikipedia 2024]|Repeating Decimals, available from:[https://en.wikipedia.org/wiki/Repeating_decimal](https://en.wikipedia.org/wiki/Repeating_decimal), [Last accessed August 2024] |
|[Albing and Vossen, 2017]|Albin, C. and Vossen, JP (2017) 6.13 Looping with Floating Point Values. In: Bleiel, J., Brown, K. and Head, R. eds. bash Cookbook: Solutions and Examples for bash Users, 2d Edition. Sebastopol: O'Reilly Media, Inc., pp.159-160|
|[Bloch 2005]|Puzzle 34, "Down for the Count", available from: [https://web.archive.org/web/20220511061752/https://wiki.sei.cmu.edu/confluence/display/java/Rule+AA.+References#RuleAA.References-Bloch05](https://web.archive.org/web/20220511061752/https://wiki.sei.cmu.edu/confluence/display/java/Rule+AA.+References#RuleAA.References-Bloch05), [Last accessed August 2024] |

Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# SPDX-FileCopyrightText: OpenSSF project contributors
# SPDX-License-Identifier: MIT
""" Compliant Code Example """
value = 0
while value <= 9:
print(value / 9)
value = value + 1

counter = 0
while counter <= 10:
value = counter/10
if value == 0.8:
print("we reached 0.8")
break
counter += 1
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# SPDX-FileCopyrightText: OpenSSF project contributors
# SPDX-License-Identifier: MIT
""" Compliant Code Example """
value = 1
while value <= 10:
print(f"{value / 10 ** 14:.14f}")
value = value + 1
counter = 1
target = 10
while counter <= target:
print(f"counter={counter / 10**16 :.20f}")
print(f" target={target / 10**16:.20f}")
counter += 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: OpenSSF project contributors
# SPDX-License-Identifier: MIT
""" Code Example """

value = 0.0
while value <= 1:
print(f"{type(value)} {value}")
value += 0.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: OpenSSF project contributors
# SPDX-License-Identifier: MIT
""" Code Example """
print(f"{1.0 + 1e-16:.20f}")
print(f"{1.0 + 1e-15:.20f}")
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# SPDX-FileCopyrightText: OpenSSF project contributors
# SPDX-License-Identifier: MIT
""" Non-compliant Code Example """
value = float(0.0)
while value <= 1:
print(value)
value = value + float(1.0/9.0)
counter = 0.0
while counter <= 1.0:
if counter == 0.8:
print("we reached 0.8")
break # never going to reach this
counter += 0.1
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# SPDX-FileCopyrightText: OpenSSF project contributors
# SPDX-License-Identifier: MIT
""" Non-compliant Code Example """
value = float(1.0) + float("1e-18")
target = float(1.0) + float("1e-17")
while value <= target:
print(value)
value = value + float("1e-18")

counter = 1.0 + 1e-16
target = 1.0 + 1e-15
while counter <= target: # never ends
print(f"counter={counter / 10**16 :.20f}")
print(f" target={target / 10**16:.20f}")
counter += 1e-16
2 changes: 1 addition & 1 deletion docs/Secure-Coding-Guide-for-Python/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ It is **not production code** and requires code-style or python best practices t
|[CWE-664: Improper Control of a Resource Through its Lifetime](https://cwe.mitre.org/data/definitions/664.html)|Prominent CVE|
|:-----------------------------------------------------------------------------------------------------------------------------------------------|:----|
|[CWE-134: Use of Externally-Controlled Format String](CWE-664/CWE-134/README.md)|[CVE-2022-27177](https://www.cvedetails.com/cve/CVE-2022-27177/),<br/>CVSSv3.1: **9.8**,<br/>EPSS: **00.37** (01.12.2023)|
|[CWE-197: Numeric Truncation Error](CWE-664/CWE-197/.)||
|[CWE-197: Numeric Truncation Error](CWE-664/CWE-197/README.md)||
|[CWE-197: Control rounding when converting to less precise numbers](CWE-664/CWE-197/01/.)||
|[CWE-400: Uncontrolled Resource Consumption](CWE-664/CWE-400/README.md)||
|[CWE-409: Improper Handling of Highly Compressed Data (Data Amplification)](CWE-664/CWE-409/.)||
Expand Down
Loading