#!/usr/bin/env python """ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission The last branch of main()'s unhandled-exception chain in sqlmap.py: when the experimental Python JIT is turned on, a crash gets the tier-2 advisory (Reference: 'https://github.com/python/cpython/issues/156319') instead of the automatic issue-creation prompt - but the traceback is still printed, so a genuine sqlmap bug is not swallowed. Driven through a subprocess because it is main()'s own except chain under test. """ import os import subprocess import sys import unittest ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Forces a crash inside init(), i.e. the same place #6117 landed, without needing a target. # NOTE: the injected fault must raise on Python 2 as well - 'a' < 128 does NOT (Python 2 orders # mismatched types by type name and quietly returns a bool), which let a broken run reach the # network on the PyPy-2.7 job instead of the exception handler under test. # codeIsModified() is neutralized because its branch sits earlier in the chain and would # otherwise win on any working tree with uncommitted edits (or a stale txt/checksum.md5). DRIVER = """ import sys sys.path.insert(0, %r) sys.argv = ["sqlmap.py", "-u", "http://127.0.0.1/?id=1", "--batch"] import sqlmap import lib.core.option sqlmap.codeIsModified = lambda: False lib.core.option.loadPayloads = lambda: iter([])() sqlmap.main() """ % ROOT ADVISORY = "experimental Python JIT compiler is turned on" TRACEBACK = "TypeError" PROMPT = "automatically create a new (anonymized) issue" def _run(jit): env = dict(os.environ) env["PYTHON_JIT"] = jit proc = subprocess.Popen([sys.executable, "-c", DRIVER], cwd=ROOT, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out = proc.communicate(input=b"")[0] return out.decode("utf-8", "replace") def _jitEnabled(jit): """Whether the guard in sqlmap.py would consider the JIT enabled (mirrors its condition).""" code = ("import sys, os; print(sys._jit.is_enabled() if hasattr(sys, '_jit') " "else (sys.version_info >= (3, 13) and os.environ.get('PYTHON_JIT') == '1'))") env = dict(os.environ) env["PYTHON_JIT"] = jit return subprocess.check_output([sys.executable, "-c", code], env=env).strip() == b"True" class TestJitGuard(unittest.TestCase): def test_advisory_replaces_issue_prompt_but_keeps_traceback(self): if not _jitEnabled('1'): self.skipTest("interpreter does not report the JIT as enabled") out = _run('1') self.assertIn(ADVISORY, out) self.assertIn(TRACEBACK, out) # a real bug must still be visible in full self.assertNotIn(PROMPT, out) # ... but not auto-filed while the JIT is on def test_normal_path_is_untouched_without_jit(self): if _jitEnabled('0'): self.skipTest("JIT stays enabled with PYTHON_JIT=0 on this build") out = _run('0') self.assertNotIn(ADVISORY, out) self.assertIn(TRACEBACK, out) self.assertIn(PROMPT, out) # Tool id 3 is unused by CPython's own well-known ids (DEBUGGER=0, COVERAGE=1, PROFILER=2, OPTIMIZER=5) _TEST_TOOL_ID = 3 _MONITORING_DRIVER = """ import sys sys.path.insert(0, %r) sys.monitoring.use_tool_id(%d, "test-tool") sys.monitoring.set_events(%d, sys.monitoring.events.PY_START) import sqlmap print(sys.monitoring.get_tool(%d)) 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 - 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_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") tool, events = _monitoringStateAfterImport('1') self.assertEqual(tool, "test-tool") # still registered - its own owner can still free it 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__": unittest.main()