From 4d677077e1c22b93d45945526ec647e82ef976e7 Mon Sep 17 00:00:00 2001 From: Helge Wehder Date: Wed, 9 Oct 2024 16:18:27 +0100 Subject: [PATCH] Adding documentation for CWE-197 01 as part of #531 to GitHub Signed-off-by: Helge Wehder --- .../CWE-664/CWE-197/01/README.md | 105 ++++++++++++++++++ .../CWE-664/CWE-197/01/compliant01.py | 9 +- .../CWE-664/CWE-197/01/example01.py | 17 +++ .../CWE-664/CWE-197/01/example02.py | 9 ++ .../CWE-664/CWE-197/01/noncompliant01.py | 8 +- docs/Secure-Coding-Guide-for-Python/readme.md | 2 +- 6 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/README.md create mode 100644 docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/example01.py create mode 100644 docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/example02.py diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/README.md b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/README.md new file mode 100644 index 00000000..7ad9bb8e --- /dev/null +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/README.md @@ -0,0 +1,105 @@ +# CWE-197: Control rounding when converting to less precise numbers + +While defensive coding requires enforcing types, it is important to make conscious design decisions on how conversions are rounded. + +The `example01.py` code demonstrates how `int()` behaves differently to `round()`. + +[*example01.py:*](example01.py) + +```py +""" Code Example """ + +print(int(0.5)) # prints 0 +print(int(1.5)) # prints 1 +print(int(1.45)) # prints 1 +print(int(1.51)) # prints 1 +print(int(-1.5)) # prints -1 + +print(round(0.5)) # prints 0 +print(round(1.5)) # prints 2 +print(round(1.45)) # prints 1 +print(round(1.51)) # prints 2 +print(round(-1.5)) # prints -2 + +print(type(round(0.5))) # prints + +``` + +The build in `round()` does not allow to specify the type of rounding in use [[python round( ) 2024]](https://docs.python.org/3/library/functions.html#round). In Python 3 the `round()` function uses "bankers' rounding" (rounds to the nearest even number in case of ties). This is different to Python 2 which always rounds away from zero. Rounding provided by the decimal module allows a choice between 8 rounding modes [python decimal 2024](https://docs.python.org/3/library/decimal.html#rounding-modes). Rounding in mathematics and science is not discussed here as it requires a deeper knowledge of computer floating-point arithmetic's. + +## Non-Compliant Code Example (float to int) + +In `noncompliant01.py` there is no conscious choice of rounding mode. + +[*noncompliant01.py:*](noncompliant01.py) + +```py +""" Non-compliant Code Example """ + +print(int(0.5)) # prints 0 +print(int(1.5)) # prints 1 +print(round(0.5)) # prints 0 +print(round(1.5)) # prints 2 +``` + +## Compliant Solution (float to int) + +Using the `Decimal` module allows more control over rounding by choosing one of the 8 rounding modes in the decimal module. + +[*compliant01.py:*](compliant01.py) + +```py +""" Compliant Code Example """ +from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN + +print(Decimal("0.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # prints 1 +print(Decimal("1.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # prints 2 +print(Decimal("0.5").quantize(Decimal("1"), rounding=ROUND_HALF_DOWN)) # prints 0 +print(Decimal("1.5").quantize(Decimal("1"), rounding=ROUND_HALF_DOWN)) # prints 1 +``` + +The `.quantize(Decimal("1")`, determines the precision to be integer and `rounding=ROUND_HALF_UP` determines the type of rounding applied. Specifying numbers as strings avoids issues such as floating-point representations in binary. + +That `Decimal` can have unexpected results when operated without `Decimal.quantize()` on floating point numbers is demonstrated in `example02.py`. + +[*example02.py:*](example02.py) + +```py +""" Code Example """ +# SPDX-FileCopyrightText: OpenSSF project contributors +# SPDX-License-Identifier: MIT +""" Code Example """ +from decimal import ROUND_HALF_UP, Decimal + +print(Decimal("0.10")) # prints 0.10 +print(Decimal(0.10)) # prints 0.1000000000000000055511151231257827021181583404541015625 +print(Decimal("0.10").quantize(Decimal("0.10"), rounding=ROUND_HALF_UP)) # prints 0.10 +print(Decimal(0.10).quantize(Decimal("0.10"), rounding=ROUND_HALF_UP)) # prints 0.10 +``` + +Initializing `Decimal` with an actual `float`, such as `0.10`, and without rounding creates an unprecise number `0.1000000000000000055511151231257827021181583404541015625` in `Python 3.9.2`. + +## 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-682, Incorrect Conversion between Numeric Types (mitre.org)](http://cwe.mitre.org/data/definitions/682.html)| +|[MITRE CWE](http://cwe.mitre.org/)|Class [CWE-197, Numeric Truncation Error](https://cwe.mitre.org/data/definitions/197.html)| +|[SEI CERT C Coding Standard](https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard)|[INT31-C. Ensure that integer conversions do not result in lost or misinterpreted data](https://wiki.sei.cmu.edu/confluence/display/c/INT31-C.+Ensure+that+integer+conversions+do+not+result+in+lost+or+misinterpreted+data)| +|[SEI CERT C Coding Standard](https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard)|[FLP34-C. Ensure that floating-point conversions are within range of the new type](https://wiki.sei.cmu.edu/confluence/display/c/FLP34-C.+Ensure+that+floating-point+conversions+are+within+range+of+the+new+type)| +|[ISO/IEC TR 24772:2019]|Programming languages — Guidance to avoiding vulnerabilities in programming languages, available from [https://www.iso.org/standard/71091.html](https://www.iso.org/standard/71091.html)| +|[SEI CERT Coding Standard for Java](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java)|[NUM12-J. Ensure conversions of numeric types to narrower types do not result in lost or misinterpreted data](https://wiki.sei.cmu.edu/confluence/display/java/NUM12-J.+Ensure+conversions+of+numeric+types+to+narrower+types+do+not+result+in+lost+or+misinterpreted+data)| + +## Biblography + +||| +|:---|:---| +|[python round( ) 2024]|python round( ), available from: [https://docs.python.org/3/library/functions.html#round](https://docs.python.org/3/library/functions.html#round), [Last accessed June 2024] | +|[python decimal 2024]|Python decimal module, available from: [https://docs.python.org/3/library/decimal.html#rounding-modes](https://docs.python.org/3/library/decimal.html#rounding-modes)| diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/compliant01.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/compliant01.py index f84f2c35..6f0d07e4 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/compliant01.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/compliant01.py @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: OpenSSF project contributors # SPDX-License-Identifier: MIT """ Compliant Code Example """ -foo = int(round(0.9)) -type(foo) # class int -print(foo) # prints 1 +from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN + +print(Decimal("0.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # prints 1 +print(Decimal("1.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # prints 2 +print(Decimal("0.5").quantize(Decimal("1"), rounding=ROUND_HALF_DOWN)) # prints 0 +print(Decimal("1.5").quantize(Decimal("1"), rounding=ROUND_HALF_DOWN)) # prints 1 diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/example01.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/example01.py new file mode 100644 index 00000000..d5a87ae3 --- /dev/null +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/example01.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: OpenSSF project contributors +# SPDX-License-Identifier: MIT +""" Code Example """ + +print(int(0.5)) # prints 0 +print(int(1.5)) # prints 1 +print(int(1.45)) # prints 1 +print(int(1.51)) # prints 1 +print(int(-1.5)) # prints -1 + +print(round(0.5)) # prints 0 +print(round(1.5)) # prints 2 +print(round(1.45)) # prints 1 +print(round(1.51)) # prints 2 +print(round(-1.5)) # prints -2 + +print(type(round(0.5))) # prints diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/example02.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/example02.py new file mode 100644 index 00000000..3900a3d2 --- /dev/null +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/example02.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: OpenSSF project contributors +# SPDX-License-Identifier: MIT +""" Code Example """ +from decimal import ROUND_HALF_UP, Decimal + +print(Decimal("0.10")) # prints 0.10 +print(Decimal(0.10)) # prints 0.1000000000000000055511151231257827021181583404541015625 +print(Decimal("0.10").quantize(Decimal("0.10"), rounding=ROUND_HALF_UP)) # prints 0.10 +print(Decimal(0.10).quantize(Decimal("0.10"), rounding=ROUND_HALF_UP)) # prints 0.10 diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/noncompliant01.py b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/noncompliant01.py index 570e550d..3862e2c7 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/noncompliant01.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-197/01/noncompliant01.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: OpenSSF project contributors # SPDX-License-Identifier: MIT """ Non-compliant Code Example """ -foo = int(0.9) -type(foo) # class int -print(foo) # prints 0 + +print(int(0.5)) # prints 0 +print(int(1.5)) # prints 1 +print(round(0.5)) # prints 0 +print(round(1.5)) # prints 2 \ No newline at end of file diff --git a/docs/Secure-Coding-Guide-for-Python/readme.md b/docs/Secure-Coding-Guide-for-Python/readme.md index 25cb766f..4de51825 100644 --- a/docs/Secure-Coding-Guide-for-Python/readme.md +++ b/docs/Secure-Coding-Guide-for-Python/readme.md @@ -42,7 +42,7 @@ It is **not production code** and requires code-style or python best practices t |:-----------------------------------------------------------------------------------------------------------------------------------------------|:----| |[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/README.md)|| -|[CWE-197: Control rounding when converting to less precise numbers](CWE-664/CWE-197/01/.)|| +|[CWE-197: Control rounding when converting to less precise numbers](CWE-664/CWE-197/01/README.md)|| |[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/.)|| |[CWE-410: Insufficient Resource Pool](CWE-664/CWE-410/README.md)||