Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1db314b

Browse files
committed
Issue #22636: Merge ctypes.util shell injection fixes from 3.5
2 parents 4074f93 + bfb15ab commit 1db314b

3 files changed

Lines changed: 85 additions & 44 deletions

File tree

Lib/ctypes/test/test_find.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
import os
2+
import os, os.path
33
import sys
44
import test.support
55
from ctypes import *
@@ -64,6 +64,11 @@ def test_gle(self):
6464
self.skipTest('lib_gle not available')
6565
self.gle.gleGetJoinStyle
6666

67+
def test_shell_injection(self):
68+
result = find_library('; echo Hello shell > ' + test.support.TESTFN)
69+
self.assertFalse(os.path.lexists(test.support.TESTFN))
70+
self.assertIsNone(result)
71+
6772
# On platforms where the default shared library suffix is '.so',
6873
# at least some libraries can be loaded as attributes of the cdll
6974
# object, since ctypes now tries loading the lib again

Lib/ctypes/util.py

Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import sys, os
2-
import contextlib
1+
import os
2+
import shutil
33
import subprocess
4+
import sys
45

56
# find_library(name) returns the pathname of a library, or None.
67
if os.name == "nt":
@@ -94,102 +95,134 @@ def find_library(name):
9495
import re, tempfile
9596

9697
def _findLib_gcc(name):
97-
expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
98-
fdout, ccout = tempfile.mkstemp()
99-
os.close(fdout)
100-
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;' \
101-
'LANG=C LC_ALL=C $CC -Wl,-t -o ' + ccout + ' 2>&1 -l' + name
98+
# Run GCC's linker with the -t (aka --trace) option and examine the
99+
# library name it prints out. The GCC command will fail because we
100+
# haven't supplied a proper program with main(), but that does not
101+
# matter.
102+
expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
103+
104+
c_compiler = shutil.which('gcc')
105+
if not c_compiler:
106+
c_compiler = shutil.which('cc')
107+
if not c_compiler:
108+
# No C compiler available, give up
109+
return None
110+
111+
temp = tempfile.NamedTemporaryFile()
102112
try:
103-
f = os.popen(cmd)
104-
try:
105-
trace = f.read()
106-
finally:
107-
rv = f.close()
113+
args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
114+
115+
env = dict(os.environ)
116+
env['LC_ALL'] = 'C'
117+
env['LANG'] = 'C'
118+
proc = subprocess.Popen(args,
119+
stdout=subprocess.PIPE,
120+
stderr=subprocess.STDOUT,
121+
env=env)
122+
with proc:
123+
trace = proc.stdout.read()
108124
finally:
109125
try:
110-
os.unlink(ccout)
126+
temp.close()
111127
except FileNotFoundError:
128+
# Raised if the file was already removed, which is the normal
129+
# behaviour of GCC if linking fails
112130
pass
113-
if rv == 10:
114-
raise OSError('gcc or cc command not found')
115131
res = re.search(expr, trace)
116132
if not res:
117133
return None
118-
return res.group(0)
134+
return os.fsdecode(res.group(0))
119135

120136

121137
if sys.platform == "sunos5":
122138
# use /usr/ccs/bin/dump on solaris
123139
def _get_soname(f):
124140
if not f:
125141
return None
126-
cmd = "/usr/ccs/bin/dump -Lpv 2>/dev/null " + f
127-
with contextlib.closing(os.popen(cmd)) as f:
128-
data = f.read()
129-
res = re.search(r'\[.*\]\sSONAME\s+([^\s]+)', data)
142+
143+
proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),
144+
stdout=subprocess.PIPE,
145+
stderr=subprocess.DEVNULL)
146+
with proc:
147+
data = proc.stdout.read()
148+
res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)
130149
if not res:
131150
return None
132-
return res.group(1)
151+
return os.fsdecode(res.group(1))
133152
else:
134153
def _get_soname(f):
135154
# assuming GNU binutils / ELF
136155
if not f:
137156
return None
138-
cmd = 'if ! type objdump >/dev/null 2>&1; then exit 10; fi;' \
139-
"objdump -p -j .dynamic 2>/dev/null " + f
140-
f = os.popen(cmd)
141-
try:
142-
dump = f.read()
143-
finally:
144-
rv = f.close()
145-
if rv == 10:
146-
raise OSError('objdump command not found')
147-
res = re.search(r'\sSONAME\s+([^\s]+)', dump)
157+
objdump = shutil.which('objdump')
158+
if not objdump:
159+
# objdump is not available, give up
160+
return None
161+
162+
proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),
163+
stdout=subprocess.PIPE,
164+
stderr=subprocess.DEVNULL)
165+
with proc:
166+
dump = proc.stdout.read()
167+
res = re.search(br'\sSONAME\s+([^\s]+)', dump)
148168
if not res:
149169
return None
150-
return res.group(1)
170+
return os.fsdecode(res.group(1))
151171

152172
if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
153173

154174
def _num_version(libname):
155175
# "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
156-
parts = libname.split(".")
176+
parts = libname.split(b".")
157177
nums = []
158178
try:
159179
while parts:
160180
nums.insert(0, int(parts.pop()))
161181
except ValueError:
162182
pass
163-
return nums or [ sys.maxsize ]
183+
return nums or [sys.maxsize]
164184

165185
def find_library(name):
166186
ename = re.escape(name)
167187
expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
168-
with contextlib.closing(os.popen('/sbin/ldconfig -r 2>/dev/null')) as f:
169-
data = f.read()
188+
expr = os.fsencode(expr)
189+
190+
proc = subprocess.Popen(('/sbin/ldconfig', '-r'),
191+
stdout=subprocess.PIPE,
192+
stderr=subprocess.DEVNULL)
193+
with proc:
194+
data = proc.stdout.read()
195+
170196
res = re.findall(expr, data)
171197
if not res:
172198
return _get_soname(_findLib_gcc(name))
173199
res.sort(key=_num_version)
174-
return res[-1]
200+
return os.fsdecode(res[-1])
175201

176202
elif sys.platform == "sunos5":
177203

178204
def _findLib_crle(name, is64):
179205
if not os.path.exists('/usr/bin/crle'):
180206
return None
181207

208+
env = dict(os.environ)
209+
env['LC_ALL'] = 'C'
210+
182211
if is64:
183-
cmd = 'env LC_ALL=C /usr/bin/crle -64 2>/dev/null'
212+
args = ('/usr/bin/crle', '-64')
184213
else:
185-
cmd = 'env LC_ALL=C /usr/bin/crle 2>/dev/null'
214+
args = ('/usr/bin/crle',)
186215

187216
paths = None
188-
with contextlib.closing(os.popen(cmd)) as f:
189-
for line in f.readlines():
217+
proc = subprocess.Popen(args,
218+
stdout=subprocess.PIPE,
219+
stderr=subprocess.DEVNULL,
220+
env=env)
221+
with proc:
222+
for line in proc.stdout:
190223
line = line.strip()
191-
if line.startswith('Default Library Path (ELF):'):
192-
paths = line.split()[4]
224+
if line.startswith(b'Default Library Path (ELF):'):
225+
paths = os.fsdecode(line).split()[4]
193226

194227
if not paths:
195228
return None

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.6.0 alpha 3
1010
Library
1111
+++++++
1212

13+
- Issue #22636: Avoid shell injection problems with
14+
ctypes.util.find_library().
15+
1316
- Issue #16182: Fix various functions in the "readline" module to use the
1417
locale encoding, and fix get_begidx() and get_endidx() to return code point
1518
indexes.

0 commit comments

Comments
 (0)