diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/README.md b/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/README.md new file mode 100644 index 00000000..6b04df50 --- /dev/null +++ b/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/README.md @@ -0,0 +1,204 @@ +# CWE-754: Improper Check for Unusual or Exceptional Conditions - Float + +Ensure to have handling for exceptional floating-point values. + +The `float` class has the capability to interpret various input values as floating-point numbers. Some special cases can interpret input values as + +* Positive Infinity +* Negative Infinity +* `NaN` (Not-a-Number) + +These floating-point class values represent numbers that fall outside the typical range and exhibit unique behaviors. `NaN` (Not a Number) lacks a defined order and is not considered equal to any value, including itself. Hence, evaluating an expression such as `NaN == NaN` returns `False`. + +## Non-Compliant Code Example + +The `noncompliant01.py` intent is to ensure that adding objects will not exceed a total weight of `100` units. Validation fails as the code to test for exceptional conditions, such as `NaN` or `infinity`, is missing. + +*[noncompliant01.py](noncompliant01.py):* + +```py +# SPDX-FileCopyrightText: OpenSSF project contributors +# SPDX-License-Identifier: MIT +"""Non-compliant Code Example""" + +import sys + + +class Package: + """Class representing a package object""" + + def __init__(self): + self.package_weight: float = 0.0 + self.max_package_weight: float = 100.0 + + def add_to_package(self, object_weight: str): + """Function for adding an object into the package""" + value = float(object_weight) + # This is dead code as value gets type cast to float, + # hence will never be equal to string "NaN" + if value == "NaN": + raise ValueError("'NaN' not a number") + # This is also dead code as value is type cast to float, + # unusual inputs like -infinity will not get caught + if isinstance(value, float) is False: + raise ValueError("not a number") + if self.package_weight + value > self.max_package_weight: + raise ValueError("Addition would exceed maximum package weight.") + + self.package_weight += value + + def get_package_weight(self): + """Getter for outputting the package's current weight""" + return self.package_weight + + +##################### +# exploiting above code example +##################### +package = Package() +print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n") +for item in [100, "-infinity", sys.float_info.max, "NaN", -100]: + print(f"package.add_to_package({item})") + try: + package.add_to_package(item) + print( + f"package.get_package_weight() = {package.get_package_weight():.2f}\n" + ) + + except Exception as e: + print(e) +``` + +Some important considerations when dealing with floating-point values from `non-complaint01.py`. + +* Setting a value above `sys.float_info.max` does not increase the held value. In some cases, incrementing `package_weight` with a high enough value may turn its value into `inf`. +* Setting the added value to `-infinity` and `+infinity` causes the value of the `package_weight` to be infinite as well. +* Adding `"NaN"`, which is not a valid value to `package_weight` will always return `"nan"`. + +**Example `noncompliant01.py` output:** + +```bash +Original package's weight is 0.00 units + +package.add_to_package(100) +package.get_package_weight() = 100.00 + +package.add_to_package(-infinity) +package.get_package_weight() = -inf + +package.add_to_package(1.7976931348623157e+308) +package.get_package_weight() = -inf + +package.add_to_package(NaN) +package.get_package_weight() = nan + +package.add_to_package(-100) +package.get_package_weight() = nan +``` + +## Compliant Solution + +Exceptional values and out-of-range values are handled in `compliant01.py`. Some negative values are also checked for due to the nature of the code example. +The `isfinite` function from the `math` library is useful for checking for `NaN`, `infinity` and `-infinity` values. `math.isfinite` checks if a value is neither `infinite` nor a `NaN`. + +Other functions from the `math` library that could be of use are `isnan`, which checks if an inputted value is `"NaN"`, and `isinf` (which checks if a value is positive or negative infinity). + +*[compliant01.py](compliant01.py):* + +```py +# SPDX-FileCopyrightText: OpenSSF project contributors +# SPDX-License-Identifier: MIT +""" Compliant Code Example """ + + +import sys +from math import isfinite, isnan +from typing import Union + + +class Package: + """Class representing a package object.""" + def __init__(self) -> None: + self.package_weight: float = 0.0 + self.max_package_weight: float = 100.0 + + def add_to_package(self, object_weight: Union[str, int, float]) -> None: + # TODO: input sanitation. + # TODO: proper exception handling + """Add an object into the package after validating its weight.""" + try: + value = float(object_weight) + except (ValueError, TypeError) as e: + raise ValueError("Input cannot be converted to a float.") from e + if isnan(value): + raise ValueError("Input is not a number") + if not isfinite(value): + raise ValueError("Input is not a finite number.") + if value < 0: + raise ValueError("Weight must be a non-negative number.") + if self.package_weight + value > self.max_package_weight: + raise ValueError("Addition would exceed maximum package weight.") + + print(f"Adding an object that weighs {value} units to package") + self.package_weight += value + + def get_package_weight(self) -> float: + """Return the package's current weight.""" + return self.package_weight + + +##################### +# exploiting above code example +##################### + + +package = Package() +print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n") +for item in [100, "-infinity", sys.float_info.max, "NaN", -100]: + print(f"package.add_to_package({item})") + try: + package.add_to_package(item) + print( + f"package.get_package_weight() = {package.get_package_weight():.2f}\n" + ) + + except Exception as e: + print(e) +``` + +This compliant code example will raise a `ValueError` for inputs that are `-infinity`, `infinity`, or `NaN`, with messages "Input is not a finite number" and "Input is not a number" respectively. It should also ensure weights are non-negative, returning "Weight must be a non-negative number" for negative inputs. + +**Example `compliant01.py` output:** + +```bash +Original package's weight is 0.00 units + +package.add_to_package(100) +Adding an object that weighs 100.0 units to package +package.get_package_weight() = 100.00 + +package.add_to_package(-infinity) +Input is not a finite number. +package.add_to_package(1.7976931348623157e+308) +Addition would exceed maximum package weight. +package.add_to_package(NaN) +Input is not a number +package.add_to_package(-100) +Weight must be a non-negative number. +``` + +## Automated Detection + +||||| +|:---|:---|:---|:---| +|Tool|Version|Checker|Description| +|bandit|1.7.4|no detection|| + +## Related Guidelines + +||| +|:---|:---| +|[SEI CERT C Coding Standard](https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard)|[FLP04-C. Check floating-point inputs for exceptional values](https://wiki.sei.cmu.edu/confluence/display/c/FLP04-C.+Check+floating-point+inputs+for+exceptional+values)| +|[SEI CERT Oracle Coding Standard for Java](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java?src=sidebar)|[NUM08-J. Check floating-point inputs for exceptional values](https://wiki.sei.cmu.edu/confluence/display/java/NUM08-J.+Check+floating-point+inputs+for+exceptional+values)| +|[CWE MITRE Pillar](http://cwe.mitre.org/)|[CWE-703: Improper Check or Handling of Exceptional Conditions](https://cwe.mitre.org/data/definitions/703.html)| +|[MITRE CWE Base](https://cwe.mitre.org/)|[CWE-754: Improper Check for Unusual or Exceptional Conditions](https://cwe.mitre.org/data/definitions/754.html)| diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/compliant01.py b/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/compliant01.py index 81dff3df..16d6d068 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/compliant01.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/compliant01.py @@ -1,46 +1,58 @@ # SPDX-FileCopyrightText: OpenSSF project contributors # SPDX-License-Identifier: MIT """ Compliant Code Example """ + + import sys -from math import isinf, isnan +from math import isfinite, isnan +from typing import Union class Package: - def __init__(self): - self.package_weight = float(1.0) - - def put_in_the_package(self, user_input): + """Class representing a package object.""" + def __init__(self) -> None: + self.package_weight: float = 0.0 + self.max_package_weight: float = 100.0 + + def add_to_package(self, object_weight: Union[str, int, float]) -> None: + """Add an object into the package after validating its weight.""" + # TODO: input sanitation. + # TODO: proper exception handling try: - value = float(user_input) - except ValueError: - raise ValueError(f"{user_input} - Input is not a number!") - - print(f"value is {value}") - - if isnan(value) or isinf(value): - raise ValueError(f"{user_input} - Input is not a real number!") - + value = float(object_weight) + except (ValueError, TypeError) as e: + raise ValueError("Input cannot be converted to a float.") from e + if isnan(value): + raise ValueError("Input is not a number") + if not isfinite(value): + raise ValueError("Input is not a finite number.") if value < 0: - raise ValueError( - f"{user_input} - Packed object weight cannot be negative!" - ) + raise ValueError("Weight must be a non-negative number.") + if self.package_weight + value > self.max_package_weight: + raise ValueError("Addition would exceed maximum package weight.") - if value >= sys.float_info.max - self.package_weight: - raise ValueError(f"{user_input} - Input exceeds acceptable range!") + print(f"Adding an object that weighs {value} units to package") self.package_weight += value - def get_package_weight(self): + def get_package_weight(self) -> float: + """Return the package's current weight.""" return self.package_weight ##################### # exploiting above code example ##################### + + package = Package() print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n") -for item in [sys.float_info.max, "infinity", "-infinity", "nan"]: +for item in [100, "-infinity", sys.float_info.max, "NaN", -100]: + print(f"package.add_to_package({item})") try: - package.put_in_the_package(item) - print(f"Current package weight = {package.get_package_weight():.2f}\n") + package.add_to_package(item) + print( + f"package.get_package_weight() = {package.get_package_weight():.2f}\n" + ) + except Exception as e: print(e) diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/noncompliant01.py b/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/noncompliant01.py index 3285a5d7..58914845 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/noncompliant01.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-703/CWE-754/noncompliant01.py @@ -1,19 +1,35 @@ # SPDX-FileCopyrightText: OpenSSF project contributors # SPDX-License-Identifier: MIT -""" Non-compliant Code Example """ +"""Non-compliant Code Example""" + import sys class Package: + """Class representing a package object""" + def __init__(self): - self.package_weight = float(1.0) + self.package_weight: float = 0.0 + self.max_package_weight: float = 100.0 - def put_in_the_package(self, object_weight): + def add_to_package(self, object_weight: str): + """Function for adding an object into the package""" value = float(object_weight) - print(f"Adding an object that weighs {value} units") + # This is dead code as value gets type cast to float, + # hence will never be equal to string "NaN" + if value == "NaN": + raise ValueError("'NaN' not a number") + # This is also dead code as value is type cast to float, + # unusual inputs like -infinity will not get caught + if isinstance(value, float) is False: + raise ValueError("not a number") + if self.package_weight + value > self.max_package_weight: + raise ValueError("Addition would exceed maximum package weight.") + self.package_weight += value def get_package_weight(self): + """Getter for outputting the package's current weight""" return self.package_weight @@ -22,9 +38,13 @@ def get_package_weight(self): ##################### package = Package() print(f"\nOriginal package's weight is {package.get_package_weight():.2f} units\n") -for item in [sys.float_info.max, "infinity", "-infinity", "nan"]: +for item in [100, "-infinity", sys.float_info.max, "NaN", -100]: + print(f"package.add_to_package({item})") try: - package.put_in_the_package(item) - print(f"Current package weight = {package.get_package_weight():.2f}\n") + package.add_to_package(item) + print( + f"package.get_package_weight() = {package.get_package_weight():.2f}\n" + ) + except Exception as e: print(e) diff --git a/docs/Secure-Coding-Guide-for-Python/readme.md b/docs/Secure-Coding-Guide-for-Python/readme.md index 3d16bcb5..7ae88049 100644 --- a/docs/Secure-Coding-Guide-for-Python/readme.md +++ b/docs/Secure-Coding-Guide-for-Python/readme.md @@ -90,7 +90,7 @@ It is __not production code__ and requires code-style or python best practices t |[CWE-230: Improper Handling of Missing Values](CWE-703/CWE-230/.)|| |[CWE-390: Detection of Error Condition without Action](CWE-703/CWE-390/README.md)|| |[CWE-392: Missing Report of Error Condition](CWE-703/CWE-392/README.md)|| -|[CWE-754: Improper Check for Unusual or Exceptional Conditions](CWE-703/CWE-754/.)|| +|[CWE-754: Improper Check for Unusual or Exceptional Conditions - float](CWE-703/CWE-754/README.md)|| |[CWE-755: Improper Handling of Exceptional Conditions](CWE-703/CWE-755/README.md)|[CVE-2024-39560](https://www.cvedetails.com/cve/CVE-2024-39560),
CVSSv3.1: __6.5__,
EPSS: __0.04__ (01.11.2024)| |[CWE-707: Improper Neutralization](https://cwe.mitre.org/data/definitions/707.html)|Prominent CVE| @@ -111,12 +111,12 @@ It is __not production code__ and requires code-style or python best practices t |Ref|Detail| |-----|-----| -|[Python 2023]|3.9 Module Index [online], available from [https://docs.python.org/3.9/py-modindex.html](https://docs.python.org/3.9/py-modindex.html) [accessed Dec 2024]| -|[mitre.org 2023]|CWE - CWE-1000: Research Concepts [online], available from [https://cwe.mitre.org/data/definitions/1000.html](https://cwe.mitre.org/data/definitions/1000.html) [accessed Dec 2024]| -|[OWASP dev 2024]|OWASP Developer Guide [online], available from [https://owasp.org/www-project-developer-guide/release/](https://owasp.org/www-project-developer-guide/release/) [accessed Dec 2024]| -|[OWASP 2021]|OWASP Top 10 ReportĀ 2021 [online], available from [https://owasp.org/www-project-top-ten/](https://owasp.org/www-project-top-ten/)| -|[MITRE Pillar 2024]|_Pillar Weakness_ [online], available form [https://cwe.mitre.org/documents/glossary/#Pillar%20Weakness](https://cwe.mitre.org/documents/glossary/#Pillar%20Weakness) [accessed Dec 2024]| -|[MITRE 2024]|CWE Top 25 [online], available form [https://cwe.mitre.org/top25/index.html](https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html) [accessed Dec 2024]| +|\[Python 2023\]|3.9 Module Index \[online\], available from [https://docs.python.org/3.9/py-modindex.html](https://docs.python.org/3.9/py-modindex.html) \[accessed Dec 2024\]| +|\[mitre.org 2023\]|CWE - CWE-1000: Research Concepts \[online\], available from [https://cwe.mitre.org/data/definitions/1000.html](https://cwe.mitre.org/data/definitions/1000.html) \[accessed Dec 2024\]| +|\[OWASP dev 2024\]|OWASP Developer Guide \[online\], available from [https://owasp.org/www-project-developer-guide/release/](https://owasp.org/www-project-developer-guide/release/) \[accessed Dec 2024\]| +|\[OWASP 2021\]|OWASP Top 10 ReportĀ 2021 \[online\], available from [https://owasp.org/www-project-top-ten/](https://owasp.org/www-project-top-ten/)| +|\[MITRE Pillar 2024\]|_Pillar Weakness_ \[online\], available form [https://cwe.mitre.org/documents/glossary/#Pillar%20Weakness](https://cwe.mitre.org/documents/glossary/#Pillar%20Weakness) \[accessed Dec 2024\]| +|\[MITRE 2024\]|CWE Top 25 \[online\], available form [https://cwe.mitre.org/top25/index.html](https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html) \[accessed Dec 2024\]| ## License