diff --git a/lib/core/settings.py b/lib/core/settings.py index d759ee0f136..951488122a9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.9.9" +VERSION = "1.10.9.12" 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 d246d33778b..d2eb8d96ab0 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -422,14 +422,6 @@ def main(): logger.critical(errMsg) raise SystemExit - elif "object is not callable" in excMsg and (sys._jit.is_enabled() if hasattr(sys, "_jit") else os.environ.get("PYTHON_JIT") == '1'): - errMsg = "there is a known issue when sqlmap is run with the experimental Python JIT compiler turned on, " - errMsg += "where a regular callable (e.g. built-in function) bogusly resolves to an unrelated object. " - errMsg += "Please turn it off (e.g. 'PYTHON_JIT=0') and try again " - errMsg += "(Reference: 'https://github.com/sqlmapproject/sqlmap/issues/6117')" - logger.critical(errMsg) - raise SystemExit - elif all(_ in excMsg for _ in ("Resource temporarily unavailable", "os.fork()", "dictionaryAttack")): errMsg = "there has been a problem while running the multiprocessing hash cracking. " errMsg += "Please rerun with option '--threads=1'" @@ -566,6 +558,17 @@ def main(): logger.critical(errMsg) raise SystemExit + elif (sys._jit.is_enabled() if hasattr(sys, "_jit") else (sys.version_info >= (3, 13) and os.environ.get("PYTHON_JIT") == '1')): + errMsg = "the experimental Python JIT compiler is turned on. CPython has an open defect where " + errMsg += "the tier-2 optimizer runs the wrong instruction stream against a correct frame, raising " + errMsg += "exceptions that the executed code cannot produce " + errMsg += "(Reference: 'https://github.com/python/cpython/issues/156319'). Please rerun with it " + errMsg += "turned off (e.g. 'PYTHON_JIT=0') and report the problem only if it still occurs" + logger.critical(errMsg) + print() + dataToStdout(excMsg) + raise SystemExit + for match in re.finditer(r'File "(.+?)", line', excMsg): file_ = match.group(1) try: diff --git a/tests/test_jit_guard.py b/tests/test_jit_guard.py new file mode 100644 index 00000000000..3a5fbde45b1 --- /dev/null +++ b/tests/test_jit_guard.py @@ -0,0 +1,85 @@ +#!/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) + + +if __name__ == "__main__": + unittest.main()