Skip to content

Commit

Permalink
Adding documentation to CWE-197 as part of ossf#531
Browse files Browse the repository at this point in the history
Signed-off-by: Helge Wehder <[email protected]>
  • Loading branch information
myteron committed Sep 5, 2024
1 parent 911a058 commit c99460a
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 18 deletions.
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

0 comments on commit c99460a

Please sign in to comment.