mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Issue #22636: Merge ctypes.util shell injection fixes from 3.5
This commit is contained in:
commit
1db314bd5c
3 changed files with 85 additions and 44 deletions
|
@ -1,5 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
import os, os.path
|
||||||
import sys
|
import sys
|
||||||
import test.support
|
import test.support
|
||||||
from ctypes import *
|
from ctypes import *
|
||||||
|
@ -64,6 +64,11 @@ class Test_OpenGL_libs(unittest.TestCase):
|
||||||
self.skipTest('lib_gle not available')
|
self.skipTest('lib_gle not available')
|
||||||
self.gle.gleGetJoinStyle
|
self.gle.gleGetJoinStyle
|
||||||
|
|
||||||
|
def test_shell_injection(self):
|
||||||
|
result = find_library('; echo Hello shell > ' + test.support.TESTFN)
|
||||||
|
self.assertFalse(os.path.lexists(test.support.TESTFN))
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
# On platforms where the default shared library suffix is '.so',
|
# On platforms where the default shared library suffix is '.so',
|
||||||
# at least some libraries can be loaded as attributes of the cdll
|
# at least some libraries can be loaded as attributes of the cdll
|
||||||
# object, since ctypes now tries loading the lib again
|
# object, since ctypes now tries loading the lib again
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import sys, os
|
import os
|
||||||
import contextlib
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
# find_library(name) returns the pathname of a library, or None.
|
# find_library(name) returns the pathname of a library, or None.
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
|
@ -94,28 +95,43 @@ elif os.name == "posix":
|
||||||
import re, tempfile
|
import re, tempfile
|
||||||
|
|
||||||
def _findLib_gcc(name):
|
def _findLib_gcc(name):
|
||||||
expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
|
# Run GCC's linker with the -t (aka --trace) option and examine the
|
||||||
fdout, ccout = tempfile.mkstemp()
|
# library name it prints out. The GCC command will fail because we
|
||||||
os.close(fdout)
|
# haven't supplied a proper program with main(), but that does not
|
||||||
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;' \
|
# matter.
|
||||||
'LANG=C LC_ALL=C $CC -Wl,-t -o ' + ccout + ' 2>&1 -l' + name
|
expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
|
||||||
|
|
||||||
|
c_compiler = shutil.which('gcc')
|
||||||
|
if not c_compiler:
|
||||||
|
c_compiler = shutil.which('cc')
|
||||||
|
if not c_compiler:
|
||||||
|
# No C compiler available, give up
|
||||||
|
return None
|
||||||
|
|
||||||
|
temp = tempfile.NamedTemporaryFile()
|
||||||
try:
|
try:
|
||||||
f = os.popen(cmd)
|
args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
|
||||||
try:
|
|
||||||
trace = f.read()
|
env = dict(os.environ)
|
||||||
finally:
|
env['LC_ALL'] = 'C'
|
||||||
rv = f.close()
|
env['LANG'] = 'C'
|
||||||
|
proc = subprocess.Popen(args,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
env=env)
|
||||||
|
with proc:
|
||||||
|
trace = proc.stdout.read()
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(ccout)
|
temp.close()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
# Raised if the file was already removed, which is the normal
|
||||||
|
# behaviour of GCC if linking fails
|
||||||
pass
|
pass
|
||||||
if rv == 10:
|
|
||||||
raise OSError('gcc or cc command not found')
|
|
||||||
res = re.search(expr, trace)
|
res = re.search(expr, trace)
|
||||||
if not res:
|
if not res:
|
||||||
return None
|
return None
|
||||||
return res.group(0)
|
return os.fsdecode(res.group(0))
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == "sunos5":
|
if sys.platform == "sunos5":
|
||||||
|
@ -123,55 +139,65 @@ elif os.name == "posix":
|
||||||
def _get_soname(f):
|
def _get_soname(f):
|
||||||
if not f:
|
if not f:
|
||||||
return None
|
return None
|
||||||
cmd = "/usr/ccs/bin/dump -Lpv 2>/dev/null " + f
|
|
||||||
with contextlib.closing(os.popen(cmd)) as f:
|
proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),
|
||||||
data = f.read()
|
stdout=subprocess.PIPE,
|
||||||
res = re.search(r'\[.*\]\sSONAME\s+([^\s]+)', data)
|
stderr=subprocess.DEVNULL)
|
||||||
|
with proc:
|
||||||
|
data = proc.stdout.read()
|
||||||
|
res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)
|
||||||
if not res:
|
if not res:
|
||||||
return None
|
return None
|
||||||
return res.group(1)
|
return os.fsdecode(res.group(1))
|
||||||
else:
|
else:
|
||||||
def _get_soname(f):
|
def _get_soname(f):
|
||||||
# assuming GNU binutils / ELF
|
# assuming GNU binutils / ELF
|
||||||
if not f:
|
if not f:
|
||||||
return None
|
return None
|
||||||
cmd = 'if ! type objdump >/dev/null 2>&1; then exit 10; fi;' \
|
objdump = shutil.which('objdump')
|
||||||
"objdump -p -j .dynamic 2>/dev/null " + f
|
if not objdump:
|
||||||
f = os.popen(cmd)
|
# objdump is not available, give up
|
||||||
try:
|
return None
|
||||||
dump = f.read()
|
|
||||||
finally:
|
proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),
|
||||||
rv = f.close()
|
stdout=subprocess.PIPE,
|
||||||
if rv == 10:
|
stderr=subprocess.DEVNULL)
|
||||||
raise OSError('objdump command not found')
|
with proc:
|
||||||
res = re.search(r'\sSONAME\s+([^\s]+)', dump)
|
dump = proc.stdout.read()
|
||||||
|
res = re.search(br'\sSONAME\s+([^\s]+)', dump)
|
||||||
if not res:
|
if not res:
|
||||||
return None
|
return None
|
||||||
return res.group(1)
|
return os.fsdecode(res.group(1))
|
||||||
|
|
||||||
if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
|
if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
|
||||||
|
|
||||||
def _num_version(libname):
|
def _num_version(libname):
|
||||||
# "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
|
# "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
|
||||||
parts = libname.split(".")
|
parts = libname.split(b".")
|
||||||
nums = []
|
nums = []
|
||||||
try:
|
try:
|
||||||
while parts:
|
while parts:
|
||||||
nums.insert(0, int(parts.pop()))
|
nums.insert(0, int(parts.pop()))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return nums or [ sys.maxsize ]
|
return nums or [sys.maxsize]
|
||||||
|
|
||||||
def find_library(name):
|
def find_library(name):
|
||||||
ename = re.escape(name)
|
ename = re.escape(name)
|
||||||
expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
|
expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
|
||||||
with contextlib.closing(os.popen('/sbin/ldconfig -r 2>/dev/null')) as f:
|
expr = os.fsencode(expr)
|
||||||
data = f.read()
|
|
||||||
|
proc = subprocess.Popen(('/sbin/ldconfig', '-r'),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
with proc:
|
||||||
|
data = proc.stdout.read()
|
||||||
|
|
||||||
res = re.findall(expr, data)
|
res = re.findall(expr, data)
|
||||||
if not res:
|
if not res:
|
||||||
return _get_soname(_findLib_gcc(name))
|
return _get_soname(_findLib_gcc(name))
|
||||||
res.sort(key=_num_version)
|
res.sort(key=_num_version)
|
||||||
return res[-1]
|
return os.fsdecode(res[-1])
|
||||||
|
|
||||||
elif sys.platform == "sunos5":
|
elif sys.platform == "sunos5":
|
||||||
|
|
||||||
|
@ -179,17 +205,24 @@ elif os.name == "posix":
|
||||||
if not os.path.exists('/usr/bin/crle'):
|
if not os.path.exists('/usr/bin/crle'):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
env = dict(os.environ)
|
||||||
|
env['LC_ALL'] = 'C'
|
||||||
|
|
||||||
if is64:
|
if is64:
|
||||||
cmd = 'env LC_ALL=C /usr/bin/crle -64 2>/dev/null'
|
args = ('/usr/bin/crle', '-64')
|
||||||
else:
|
else:
|
||||||
cmd = 'env LC_ALL=C /usr/bin/crle 2>/dev/null'
|
args = ('/usr/bin/crle',)
|
||||||
|
|
||||||
paths = None
|
paths = None
|
||||||
with contextlib.closing(os.popen(cmd)) as f:
|
proc = subprocess.Popen(args,
|
||||||
for line in f.readlines():
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
env=env)
|
||||||
|
with proc:
|
||||||
|
for line in proc.stdout:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line.startswith('Default Library Path (ELF):'):
|
if line.startswith(b'Default Library Path (ELF):'):
|
||||||
paths = line.split()[4]
|
paths = os.fsdecode(line).split()[4]
|
||||||
|
|
||||||
if not paths:
|
if not paths:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -10,6 +10,9 @@ What's New in Python 3.6.0 alpha 3
|
||||||
Library
|
Library
|
||||||
+++++++
|
+++++++
|
||||||
|
|
||||||
|
- Issue #22636: Avoid shell injection problems with
|
||||||
|
ctypes.util.find_library().
|
||||||
|
|
||||||
- Issue #16182: Fix various functions in the "readline" module to use the
|
- Issue #16182: Fix various functions in the "readline" module to use the
|
||||||
locale encoding, and fix get_begidx() and get_endidx() to return code point
|
locale encoding, and fix get_begidx() and get_endidx() to return code point
|
||||||
indexes.
|
indexes.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue