From 9a9e4bdc8c85117f8a23bd2a0a32be7165510470 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:55:40 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20DoS=20vulnerability=20via=20massive=20integer=20conver?= =?UTF-8?q?sion=20exhaustion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ManupaKDU <95234271+ManupaKDU@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ test_testping1.py | 33 +++++++++++++++++++++++++++++++++ testping1.py | 44 +++++++++++++++++++++++++++++++++----------- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index fa4d44c..b733aec 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -32,3 +32,7 @@ **Vulnerability:** Log and Argument Injection via unhandled IPv6 `scope_id` **Learning:** Python's `ipaddress.ip_address` function permits arbitrary characters (like `\n` or `;`) in the `scope_id` section of IPv6 addresses. If the IP address object is logged or used in a subprocess directly without validation, these characters can result in log injection or arbitrary argument execution. **Prevention:** Strictly validate `scope_id` of IPv6 addresses using a regex like `re.fullmatch(r'[\w\-]+', ip_obj.scope_id)` before they interact with subprocess execution, logging, or other sinks. Wrap unsanitized IP inputs in `repr()` when logging. +## 2025-02-14 - Prevent Unhandled ValueError during repr() and String Conversion +**Vulnerability:** Denial of Service (DoS) via Application Crash. +**Learning:** Python 3.11+ introduced `sys.set_int_max_str_digits` which limits the conversion between massive integers and strings (e.g., calling `repr()` on an int like `10**100000`). When untrusted large integers are passed as arguments (like `ip` or `timeout`) and later sanitized for logging via `repr()`, it raises an unhandled `ValueError` that bypasses standard exception handlers and crashes the entire worker thread pool. +**Prevention:** To prevent thread exhaustion and DoS, always explicitly enforce boundary checks (`type(var) is int` and size limits) on arbitrary inputs *before* any string formatting or `repr()` usage. As a defense in depth measure, wrap explicit `repr()` calls on untrusted dynamic inputs in a `try...except ValueError` block to provide a safe fallback string like ``. diff --git a/test_testping1.py b/test_testping1.py index 39ada39..7e7f232 100644 --- a/test_testping1.py +++ b/test_testping1.py @@ -91,6 +91,39 @@ def test_is_reachable_timeout_too_long(self, mock_call): self.assertIn("Timeout string too long", log.output[0]) mock_call.assert_not_called() + @patch('testping1.subprocess.call') + def test_is_reachable_massive_int_ip(self, mock_call): + """Test is_reachable rejects massive integers for IP to prevent DoS.""" + with self.assertLogs(level='ERROR') as log: + self.assertFalse(is_reachable(10**10000)) + self.assertIn("IP address integer out of range", log.output[0]) + mock_call.assert_not_called() + + @patch('testping1.subprocess.call') + def test_is_reachable_massive_int_timeout(self, mock_call): + """Test is_reachable rejects massive integers for timeout to prevent DoS.""" + with self.assertLogs(level='ERROR') as log: + self.assertFalse(is_reachable('192.168.1.1', timeout=10**10000)) + self.assertIn("Timeout integer out of range", log.output[0]) + mock_call.assert_not_called() + + @patch('testping1.subprocess.call') + def test_is_reachable_unrepresentable_repr(self, mock_call): + """Test is_reachable handles ValueError during repr() safely.""" + class MaliciousRepr: + def __repr__(self): + raise ValueError("Exceeds limit") + + with self.assertLogs(level='ERROR') as log: + self.assertFalse(is_reachable(MaliciousRepr())) + self.assertIn("Invalid IP address format: ", log.output[0]) + mock_call.assert_not_called() + + with self.assertLogs(level='ERROR') as log: + self.assertFalse(is_reachable('192.168.1.1', timeout=MaliciousRepr())) + self.assertIn("Invalid timeout value: ", log.output[0]) + mock_call.assert_not_called() + @patch('testping1.subprocess.call') def test_is_reachable_secure_error_handling(self, mock_call): """Test is_reachable handles OSError securely without leaking exceptions.""" diff --git a/testping1.py b/testping1.py index dd0126b..f729705 100644 --- a/testping1.py +++ b/testping1.py @@ -39,6 +39,12 @@ def is_reachable(ip, timeout=1): if isinstance(ip, (ipaddress.IPv4Address, ipaddress.IPv6Address)): ip_obj = ip else: + # 🛡️ Sentinel: Prevent integer string conversion exhaustion (DoS) + # Check integer bounds before passing to ipaddress to avoid ValueError + if type(ip) is int and (ip < 0 or ip > (2**128 - 1)): + logging.error("IP address integer out of range") + return False + # 🛡️ Sentinel: Add input length limit to prevent resource exhaustion (DoS) # The ipaddress module can take significant time to parse extremely long strings if isinstance(ip, str) and len(ip) > 100: @@ -53,7 +59,12 @@ def is_reachable(ip, timeout=1): ip_obj = ipaddress.ip_address(ip) except (ValueError, TypeError): # 🛡️ Sentinel: Sanitize log input to prevent CRLF/Log Injection - logging.error(f"Invalid IP address format: {repr(ip)}") + # Handle ValueError from repr() on massive data structures + try: + safe_ip = repr(ip) + except ValueError: + safe_ip = "" + logging.error(f"Invalid IP address format: {safe_ip}") return False # 🛡️ Sentinel: Prevent Log and Argument Injection via IPv6 scope_id @@ -62,7 +73,11 @@ def is_reachable(ip, timeout=1): # injection in the subprocess call or log injection. if getattr(ip_obj, 'scope_id', None): if not re.fullmatch(r'[\w\-]+', ip_obj.scope_id): - logging.error(f"Invalid IPv6 scope ID: {repr(ip)}") + try: + safe_ip = repr(ip) + except ValueError: + safe_ip = "" + logging.error(f"Invalid IPv6 scope ID: {safe_ip}") return False # 🛡️ Sentinel: Prevent Server-Side Request Forgery (SSRF) @@ -72,18 +87,21 @@ def is_reachable(ip, timeout=1): # 🛡️ Sentinel: Sanitize log input using repr() to prevent CRLF/Log Injection # IPv6 addresses can contain an arbitrary scope ID (e.g., %eth0\r\n) which is # not sanitized by ipaddress.ip_address() and could allow log spoofing. - logging.error(f"IP address not allowed for scanning: {repr(ip)}") + try: + safe_ip = repr(ip) + except ValueError: + safe_ip = "" + logging.error(f"IP address not allowed for scanning: {safe_ip}") return False - # 🛡️ Sentinel: Validate timeout length to prevent CPU exhaustion (DoS) - # Python's int() conversion for massive strings has O(N^2) complexity. - if isinstance(timeout, str) and len(timeout) > 100: - logging.error("Timeout string too long") + # 🛡️ Sentinel: Prevent integer string conversion exhaustion (DoS) + # Reject massive integers before passing them to string formatting/repr() + if type(timeout) is int and (timeout < 0 or timeout > 100): + logging.error("Timeout integer out of range") return False - # 🛡️ Sentinel: Validate timeout to prevent argument injection or errors - # 🛡️ Sentinel: Add input length limit to prevent CPU exhaustion (DoS) - # Python's int() conversion algorithm can be exploited with very long strings + # 🛡️ Sentinel: Validate timeout length to prevent CPU exhaustion (DoS) + # Python's int() conversion for massive strings has O(N^2) complexity. if isinstance(timeout, str) and len(timeout) > 100: logging.error("Timeout string too long") return False @@ -97,7 +115,11 @@ def is_reachable(ip, timeout=1): # Inputs originating from JSON can include Infinity (parsed as float) # which raises OverflowError when cast to int and crashes threads. # 🛡️ Sentinel: Sanitize log input to prevent CRLF/Log Injection - logging.error(f"Invalid timeout value: {repr(timeout)}") + try: + safe_timeout = repr(timeout) + except ValueError: + safe_timeout = "" + logging.error(f"Invalid timeout value: {safe_timeout}") return False # ⚡ Bolt: Optimized ping execution by adding `-n` and `-q` flags.