From c99460a5276a8c9c3db8a37df09cc68d180cd42d Mon Sep 17 00:00:00 2001 From: Helge Wehder Date: Thu, 5 Sep 2024 17:25:36 +0100 Subject: [PATCH] Adding documentation to CWE-197 as part of #531 Signed-off-by: Helge Wehder --- .../CWE-664/CWE-197/README.md | 154 ++++++++++++++++++ .../CWE-664/CWE-197/compliant01.py | 12 +- .../CWE-664/CWE-197/compliant02.py | 10 +- .../CWE-664/CWE-197/example01.py | 8 + .../CWE-664/CWE-197/example02.py | 5 + .../CWE-664/CWE-197/noncompliant01.py | 10 +- .../CWE-664/CWE-197/noncompliant02.py | 12 +- docs/Secure-Coding-Guide-for-Python/readme.md | 2 +- 8 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/README.md create mode 100644 docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/example01.py create mode 100644 docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/example02.py diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/README.md b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/README.md new file mode 100644 index 00000000..6b6fbcc0 --- /dev/null +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/README.md @@ -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 + 0.0 + 0.1 + 0.2 + 0.30000000000000004 + 0.4 + 0.5 + 0.6 + 0.7 + 0.7999999999999999 + 0.8999999999999999 + 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] | + diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/compliant01.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/compliant01.py index afebfbbb..eed9184b 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/compliant01.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/compliant01.py @@ -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 diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/compliant02.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/compliant02.py index 59474319..f1d6f932 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/compliant02.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/compliant02.py @@ -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 diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/example01.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/example01.py new file mode 100644 index 00000000..9bb50c7a --- /dev/null +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/example01.py @@ -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 diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/example02.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/example02.py new file mode 100644 index 00000000..304e8eaf --- /dev/null +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/example02.py @@ -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}") diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/noncompliant01.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/noncompliant01.py index 2f8e3d1f..f5175a21 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/noncompliant01.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/noncompliant01.py @@ -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 diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/noncompliant02.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/noncompliant02.py index a034d0ed..1f6bd0ed 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/noncompliant02.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/noncompliant02.py @@ -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 diff --git a/docs/Secure-Coding-Guide-for-Python/readme.md b/docs/Secure-Coding-Guide-for-Python/readme.md index 29cf6db9..6c27955e 100644 --- a/docs/Secure-Coding-Guide-for-Python/readme.md +++ b/docs/Secure-Coding-Guide-for-Python/readme.md @@ -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/),
CVSSv3.1: **9.8**,
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/.)||