diff --git a/lib/core/settings.py b/lib/core/settings.py index db16a47e9..10a02f6d5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.9.15" +VERSION = "1.10.9.16" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/sqlmap.py b/sqlmap.py index 344d44e4b..feacbc82c 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -8,16 +8,17 @@ See the file 'LICENSE' for copying permission from __future__ import print_function try: + import os import sys sys.dont_write_bytecode = True # Reference: https://github.com/python/cpython/issues/156319 - CPython's tier-2 optimizer can - # corrupt frame locals (raising exceptions the executed code cannot produce) whenever an active - # sys.monitoring tool (an attached debugger/profiler/coverage run) coincides with enough hot - # code. sqlmap never needs one attached during a scan, so silence any already-registered tool - # up front, before any hot code runs (name stays registered so the owning tool can still free it) - if hasattr(sys, "monitoring"): + # corrupt frame locals (raising exceptions the executed code cannot produce), but only when the + # experimental JIT is on AND a sys.monitoring tool (debugger/profiler/coverage run) is active at + # the same time - neither alone triggers it. So only silence monitoring when the JIT is also on; + # otherwise leave it alone (e.g. a legitimate `coverage run sqlmap.py ...` must keep working). + if hasattr(sys, "monitoring") and (sys._jit.is_enabled() if hasattr(sys, "_jit") else (sys.version_info >= (3, 13) and os.environ.get("PYTHON_JIT") == '1')): for _ in range(6): # valid tool id range (Reference: https://docs.python.org/3/library/sys.monitoring.html) try: if sys.monitoring.get_tool(_) is not None: diff --git a/tests/test_jit_guard.py b/tests/test_jit_guard.py index c56271de1..6b71bc68f 100644 --- a/tests/test_jit_guard.py +++ b/tests/test_jit_guard.py @@ -95,23 +95,42 @@ print(sys.monitoring.get_events(%d)) """ % (ROOT, _TEST_TOOL_ID, _TEST_TOOL_ID, _TEST_TOOL_ID, _TEST_TOOL_ID) +def _monitoringStateAfterImport(jit): + env = dict(os.environ) + env["PYTHON_JIT"] = jit + out = subprocess.check_output([sys.executable, "-c", _MONITORING_DRIVER], cwd=ROOT, env=env) + tool, events = out.decode("utf-8").strip().splitlines() + return tool, int(events) + + class TestMonitoringGuard(unittest.TestCase): """ The other half of the cpython#156319 mitigation: the tier-2 corruption needs BOTH the JIT and - an active sys.monitoring tool (debugger/profiler/coverage) at once. sqlmap has no legitimate - reason to run a scan with one attached, so it silences any already-registered tool's events as - the very first thing at import time (Reference: 'https://github.com/python/cpython/issues/156319'). + an active sys.monitoring tool (debugger/profiler/coverage) at once - neither alone triggers it. + So sqlmap only silences an already-registered tool's events when the JIT is also on, and leaves + it alone otherwise. A plain `coverage run sqlmap.py ...` (JIT off) must keep working - which is + exactly what broke CI the first time this guard shipped unconditionally. """ - def test_active_tool_is_silenced_but_not_unregistered(self): + def test_active_tool_is_silenced_when_jit_is_on(self): if not hasattr(sys, "monitoring"): self.skipTest("interpreter has no sys.monitoring (needs 3.12+)") + if not _jitEnabled('1'): + self.skipTest("interpreter does not report the JIT as enabled") - out = subprocess.check_output([sys.executable, "-c", _MONITORING_DRIVER], cwd=ROOT) - tool, events = out.decode("utf-8").strip().splitlines() - + tool, events = _monitoringStateAfterImport('1') self.assertEqual(tool, "test-tool") # still registered - its own owner can still free it - self.assertEqual(events, "0") # events cleared to NO_EVENTS, so nothing fires + self.assertEqual(events, 0) # but events cleared to NO_EVENTS + + def test_active_tool_is_left_alone_without_jit(self): + if not hasattr(sys, "monitoring"): + self.skipTest("interpreter has no sys.monitoring (needs 3.12+)") + if _jitEnabled('0'): + self.skipTest("JIT stays enabled with PYTHON_JIT=0 on this build") + + tool, events = _monitoringStateAfterImport('0') + self.assertEqual(tool, "test-tool") + self.assertNotEqual(events, 0) # untouched - e.g. a real `coverage run` must keep working if __name__ == "__main__":