cpython: 0715d403cae2 (original) (raw)
--- a/Lib/ctypes/test/test_find.py +++ b/Lib/ctypes/test/test_find.py @@ -1,5 +1,5 @@ import unittest -import os +import os, os.path import sys import test.support from ctypes import * @@ -64,6 +64,11 @@ class Test_OpenGL_libs(unittest.TestCase self.skipTest('lib_gle not available') self.gle.gleGetJoinStyle
- def test_shell_injection(self):
result = find_library('; echo Hello shell > ' + test.support.TESTFN)[](#l1.15)
self.assertFalse(os.path.lexists(test.support.TESTFN))[](#l1.16)
self.assertIsNone(result)[](#l1.17)
On platforms where the default shared library suffix is '.so',
at least some libraries can be loaded as attributes of the cdll
object, since ctypes now tries loading the lib again
--- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py @@ -1,6 +1,7 @@ -import sys, os -import contextlib +import os +import shutil import subprocess +import sys
find_library(name) returns the pathname of a library, or None.
if os.name == "nt": @@ -94,28 +95,43 @@ elif os.name == "posix": import re, tempfile def _findLib_gcc(name):
expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)[](#l2.17)
fdout, ccout = tempfile.mkstemp()[](#l2.18)
os.close(fdout)[](#l2.19)
cmd = 'if type gcc >/dev/null 2>&1; then CC=gcc; elif type cc >/dev/null 2>&1; then CC=cc;else exit 10; fi;' \[](#l2.20)
'LANG=C LC_ALL=C $CC -Wl,-t -o ' + ccout + ' 2>&1 -l' + name[](#l2.21)
# Run GCC's linker with the -t (aka --trace) option and examine the[](#l2.22)
# library name it prints out. The GCC command will fail because we[](#l2.23)
# haven't supplied a proper program with main(), but that does not[](#l2.24)
# matter.[](#l2.25)
expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))[](#l2.26)
c_compiler = shutil.which('gcc')[](#l2.28)
if not c_compiler:[](#l2.29)
c_compiler = shutil.which('cc')[](#l2.30)
if not c_compiler:[](#l2.31)
# No C compiler available, give up[](#l2.32)
return None[](#l2.33)
temp = tempfile.NamedTemporaryFile()[](#l2.35) try:[](#l2.36)
f = os.popen(cmd)[](#l2.37)
try:[](#l2.38)
trace = f.read()[](#l2.39)
finally:[](#l2.40)
rv = f.close()[](#l2.41)
args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name][](#l2.42)
env = dict(os.environ)[](#l2.44)
env['LC_ALL'] = 'C'[](#l2.45)
env['LANG'] = 'C'[](#l2.46)
proc = subprocess.Popen(args,[](#l2.47)
stdout=subprocess.PIPE,[](#l2.48)
stderr=subprocess.STDOUT,[](#l2.49)
env=env)[](#l2.50)
with proc:[](#l2.51)
trace = proc.stdout.read()[](#l2.52) finally:[](#l2.53) try:[](#l2.54)
os.unlink(ccout)[](#l2.55)
temp.close()[](#l2.56) except FileNotFoundError:[](#l2.57)
# Raised if the file was already removed, which is the normal[](#l2.58)
# behaviour of GCC if linking fails[](#l2.59) pass[](#l2.60)
if rv == 10:[](#l2.61)
raise OSError('gcc or cc command not found')[](#l2.62) res = re.search(expr, trace)[](#l2.63) if not res:[](#l2.64) return None[](#l2.65)
return res.group(0)[](#l2.66)
return os.fsdecode(res.group(0))[](#l2.67)
if sys.platform == "sunos5": @@ -123,55 +139,65 @@ elif os.name == "posix": def _get_soname(f): if not f: return None
cmd = "/usr/ccs/bin/dump -Lpv 2>/dev/null " + f[](#l2.75)
with contextlib.closing(os.popen(cmd)) as f:[](#l2.76)
data = f.read()[](#l2.77)
res = re.search(r'\[.*\]\sSONAME\s+([^\s]+)', data)[](#l2.78)
proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),[](#l2.80)
stdout=subprocess.PIPE,[](#l2.81)
stderr=subprocess.DEVNULL)[](#l2.82)
with proc:[](#l2.83)
data = proc.stdout.read()[](#l2.84)
res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)[](#l2.85) if not res:[](#l2.86) return None[](#l2.87)
return res.group(1)[](#l2.88)
else: def _get_soname(f): # assuming GNU binutils / ELF if not f: return Nonereturn os.fsdecode(res.group(1))[](#l2.89)
cmd = 'if ! type objdump >/dev/null 2>&1; then exit 10; fi;' \[](#l2.95)
"objdump -p -j .dynamic 2>/dev/null " + f[](#l2.96)
f = os.popen(cmd)[](#l2.97)
try:[](#l2.98)
dump = f.read()[](#l2.99)
finally:[](#l2.100)
rv = f.close()[](#l2.101)
if rv == 10:[](#l2.102)
raise OSError('objdump command not found')[](#l2.103)
res = re.search(r'\sSONAME\s+([^\s]+)', dump)[](#l2.104)
objdump = shutil.which('objdump')[](#l2.105)
if not objdump:[](#l2.106)
# objdump is not available, give up[](#l2.107)
return None[](#l2.108)
proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),[](#l2.110)
stdout=subprocess.PIPE,[](#l2.111)
stderr=subprocess.DEVNULL)[](#l2.112)
with proc:[](#l2.113)
dump = proc.stdout.read()[](#l2.114)
res = re.search(br'\sSONAME\s+([^\s]+)', dump)[](#l2.115) if not res:[](#l2.116) return None[](#l2.117)
return res.group(1)[](#l2.118)
return os.fsdecode(res.group(1))[](#l2.119)
if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")): def _num_version(libname): # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
parts = libname.split(".")[](#l2.125)
parts = libname.split(b".")[](#l2.126) nums = [][](#l2.127) try:[](#l2.128) while parts:[](#l2.129) nums.insert(0, int(parts.pop()))[](#l2.130) except ValueError:[](#l2.131) pass[](#l2.132)
return nums or [ sys.maxsize ][](#l2.133)
return nums or [sys.maxsize][](#l2.134)
def find_library(name): ename = re.escape(name) expr = r':-l%s.\S+ => \S*/(lib%s.\S+)' % (ename, ename)
with contextlib.closing(os.popen('/sbin/ldconfig -r 2>/dev/null')) as f:[](#l2.139)
data = f.read()[](#l2.140)
expr = os.fsencode(expr)[](#l2.141)
proc = subprocess.Popen(('/sbin/ldconfig', '-r'),[](#l2.143)
stdout=subprocess.PIPE,[](#l2.144)
stderr=subprocess.DEVNULL)[](#l2.145)
with proc:[](#l2.146)
data = proc.stdout.read()[](#l2.147)
+ res = re.findall(expr, data) if not res: return _get_soname(_findLib_gcc(name)) res.sort(key=_num_version)
return res[-1][](#l2.153)
return os.fsdecode(res[-1])[](#l2.154)
elif sys.platform == "sunos5": @@ -179,17 +205,24 @@ elif os.name == "posix": if not os.path.exists('/usr/bin/crle'): return None
env = dict(os.environ)[](#l2.162)
env['LC_ALL'] = 'C'[](#l2.163)
cmd = 'env LC_ALL=C /usr/bin/crle -64 2>/dev/null'[](#l2.166)
args = ('/usr/bin/crle', '-64')[](#l2.167) else:[](#l2.168)
cmd = 'env LC_ALL=C /usr/bin/crle 2>/dev/null'[](#l2.169)
args = ('/usr/bin/crle',)[](#l2.170)
with contextlib.closing(os.popen(cmd)) as f:[](#l2.173)
for line in f.readlines():[](#l2.174)
proc = subprocess.Popen(args,[](#l2.175)
stdout=subprocess.PIPE,[](#l2.176)
stderr=subprocess.DEVNULL,[](#l2.177)
env=env)[](#l2.178)
with proc:[](#l2.179)
for line in proc.stdout:[](#l2.180) line = line.strip()[](#l2.181)
if line.startswith('Default Library Path (ELF):'):[](#l2.182)
paths = line.split()[4][](#l2.183)
if line.startswith(b'Default Library Path (ELF):'):[](#l2.184)
paths = os.fsdecode(line).split()[4][](#l2.185)
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Core and Builtins Library ------- +- Issue #22636: Avoid shell injection problems with