Minor patch for shellExec (adding timeout)
Some checks failed
/ build (macos-latest, 3.8) (push) Has been cancelled
/ build (ubuntu-latest, pypy-2.7) (push) Has been cancelled
/ build (windows-latest, 3.14) (push) Has been cancelled

This commit is contained in:
Miroslav Štampar 2026-09-25 17:01:07 +02:00
parent a3d29174fb
commit c36d056481
3 changed files with 44 additions and 5 deletions

View file

@ -27,6 +27,7 @@ import platform
import posixpath
import random
import re
import signal
import socket
import string
import subprocess
@ -2476,22 +2477,58 @@ def getConsoleWidth(default=80):
return width or default
def shellExec(cmd):
def shellExec(cmd, timeout=None):
"""
Executes arbitrary shell command
Executes arbitrary shell command, optionally bounded by 'timeout' seconds - killing (and
flagging) a hung child instead of blocking forever, as callers otherwise have no other
watchdog around this call (e.g. --vuln-test runs one such call per entry, unattended)
>>> shellExec('echo 1').strip() == '1'
True
"""
retVal = ""
timedOut = []
try:
retVal = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] or ""
popenKwargs = {"shell": True, "stdout": subprocess.PIPE, "stderr": subprocess.STDOUT}
if timeout:
# shell=True's Popen.pid is the shell, not the (possibly grandchild) command it runs -
# killing just that pid leaves the real child holding the stdout pipe open, so
# communicate() keeps blocking past the deadline; run it in its own group/session instead
# so the whole tree can be killed at once
if IS_WIN:
popenKwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
else:
popenKwargs["preexec_fn"] = os.setsid
process = subprocess.Popen(cmd, **popenKwargs)
def _kill():
timedOut.append(True)
try:
if IS_WIN:
subprocess.call(["taskkill", "/F", "/T", "/PID", str(process.pid)])
else:
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
except Exception:
pass
timer = threading.Timer(timeout, _kill) if timeout else None
if timer:
timer.daemon = True
timer.start()
retVal = process.communicate()[0] or ""
if timer:
timer.cancel()
except Exception as ex:
retVal = getSafeExString(ex)
finally:
retVal = getText(retVal)
if timedOut:
retVal += "\n[shellExec] child process tree killed after exceeding %d-second timeout" % timeout
return retVal

View file

@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.9.24"
VERSION = "1.10.9.25"
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)

View file

@ -268,7 +268,9 @@ def vulnTest(tests=None, label="vuln"):
os.environ["SQLMAP_UNSAFE_EVAL"] = '1'
output = shellExec(cmd)
# bounded well above the slowest known entry (GraphQL, ~96s) - a hung entry fails fast and
# visibly instead of silently burning the whole CI job's timeout (see #6129 CI investigation)
output = shellExec(cmd, timeout=180)
if not all((check in output if not check.startswith('~') else check[1:] not in output) for check in checks) or "unhandled exception" in output:
dataToStdout("---\n\n$ %s\n" % cmd)