Remove a possible security issue

sys paths are not executed anymore and use static analysis now.
This commit is contained in:
Dave Halter
2017-10-05 19:57:50 +02:00
parent aa8a6d2482
commit 7019ca643e
4 changed files with 29 additions and 41 deletions

View File

@@ -174,14 +174,6 @@ except NameError:
unicode = str
# exec function
if is_py3:
def exec_function(source, global_map):
exec(source, global_map)
else:
eval(compile("""def exec_function(source, global_map):
exec source in global_map """, 'blub', 'exec'))
# re-raise function
if is_py3:
def reraise(exception, traceback):

View File

@@ -4,9 +4,10 @@ import sys
import imp
from jedi.evaluate.site import addsitedir
from jedi._compatibility import exec_function, unicode
from jedi._compatibility import unicode
from jedi.evaluate.cache import evaluator_function_cache
from jedi.evaluate.base_context import ContextualizedNode
from jedi.evaluate.helpers import is_string
from jedi import settings
from jedi import debug
from jedi.evaluate.utils import ignored
@@ -68,7 +69,8 @@ def _get_venv_sitepackages(venv):
return p
def _abs_path(module_path, path):
def _abs_path(module_context, path):
module_path = module_context.py__file__()
if os.path.isabs(path):
return path
@@ -78,6 +80,7 @@ def _abs_path(module_path, path):
return None
base_dir = os.path.dirname(module_path)
print(base_dir, path)
return os.path.abspath(os.path.join(base_dir, path))
@@ -115,50 +118,36 @@ def _paths_from_assignment(module_context, expr_stmt):
except AssertionError:
continue
from jedi.evaluate.syntax_tree import is_string
cn = ContextualizedNode(module_context.create_context(expr_stmt), expr_stmt)
for lazy_context in cn.infer().iterate(cn):
for context in lazy_context.infer():
if is_string(context):
abs_path = _abs_path(module_context.py__file__(), context.obj)
abs_path = _abs_path(module_context, context.obj)
if abs_path is not None:
yield abs_path
def _execute_code(module_path, code):
c = "import os; from os.path import *; result=%s"
variables = {'__file__': module_path}
try:
exec_function(c % code, variables)
except Exception:
debug.warning('sys.path manipulation detected, but failed to evaluate.')
else:
try:
res = variables['result']
if isinstance(res, str):
return _abs_path(module_path, res)
except KeyError:
pass
return None
def _paths_from_list_modifications(module_path, trailer1, trailer2):
def _paths_from_list_modifications(module_context, trailer1, trailer2):
""" extract the path from either "sys.path.append" or "sys.path.insert" """
# Guarantee that both are trailers, the first one a name and the second one
# a function execution with at least one param.
if not (trailer1.type == 'trailer' and trailer1.children[0] == '.'
and trailer2.type == 'trailer' and trailer2.children[0] == '('
and len(trailer2.children) == 3):
return []
return
name = trailer1.children[1].value
if name not in ['insert', 'append']:
return []
return
arg = trailer2.children[1]
if name == 'insert' and len(arg.children) in (3, 4): # Possible trailing comma.
arg = arg.children[2]
path = _execute_code(module_path, arg.get_code())
return [] if path is None else [path]
for context in module_context.create_context(arg).eval_node(arg):
if is_string(context):
abs_path = _abs_path(module_context, context.obj)
if abs_path is not None:
yield abs_path
def check_sys_path_modifications(module_context):
@@ -190,7 +179,7 @@ def check_sys_path_modifications(module_context):
if len(power.children) >= 4:
added.extend(
_paths_from_list_modifications(
module_context.py__file__(), *power.children[2:4]
module_context, *power.children[2:4]
)
)
elif expr_stmt is not None and expr_stmt.type == 'expr_stmt':

View File

@@ -7,8 +7,6 @@ sys.path.insert(0, '../../jedi')
sys.path.append(dirname(os.path.abspath('thirdparty' + os.path.sep + 'asdf')))
# modifications, that should fail:
# because of sys module
sys.path.append(sys.path[1] + '/thirdparty')
# syntax err
sys.path.append('a' +* '/thirdparty')
@@ -18,8 +16,9 @@ import evaluate
#? ['evaluator_function_cache']
evaluate.Evaluator_fu
#? ['jedi_']
# Those don't work because dirname and abspath are not properly understood.
##? ['jedi_']
import jedi_
#? ['el']
##? ['el']
jedi_.el

View File

@@ -4,10 +4,18 @@ Tests of ``jedi.api.Interpreter``.
import pytest
import jedi
from jedi._compatibility import is_py33, exec_function, py_version
from jedi._compatibility import is_py33, is_py3
from jedi.evaluate.compiled import mixed
if is_py3:
def exec_(source, global_map):
exec(source, global_map)
else:
eval(compile("""def exec_(source, global_map):
exec source in global_map """, 'blub', 'exec'))
class _GlobalNameSpace():
class SideEffectContainer():
pass
@@ -247,7 +255,7 @@ def test_completion_param_annotations():
# Need to define this function not directly in Python. Otherwise Jedi is to
# clever and uses the Python code instead of the signature object.
code = 'def foo(a: 1, b: str, c: int = 1.0): pass'
exec_function(code, locals())
exec_(code, locals())
script = jedi.Interpreter('foo', [locals()])
c, = script.completions()
a, b, c = c.params