1
0
forked from VimPlug/jedi

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 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 # re-raise function
if is_py3: if is_py3:
def reraise(exception, traceback): def reraise(exception, traceback):

View File

@@ -4,9 +4,10 @@ import sys
import imp import imp
from jedi.evaluate.site import addsitedir 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.cache import evaluator_function_cache
from jedi.evaluate.base_context import ContextualizedNode from jedi.evaluate.base_context import ContextualizedNode
from jedi.evaluate.helpers import is_string
from jedi import settings from jedi import settings
from jedi import debug from jedi import debug
from jedi.evaluate.utils import ignored from jedi.evaluate.utils import ignored
@@ -68,7 +69,8 @@ def _get_venv_sitepackages(venv):
return p 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): if os.path.isabs(path):
return path return path
@@ -78,6 +80,7 @@ def _abs_path(module_path, path):
return None return None
base_dir = os.path.dirname(module_path) base_dir = os.path.dirname(module_path)
print(base_dir, path)
return os.path.abspath(os.path.join(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: except AssertionError:
continue continue
from jedi.evaluate.syntax_tree import is_string
cn = ContextualizedNode(module_context.create_context(expr_stmt), expr_stmt) cn = ContextualizedNode(module_context.create_context(expr_stmt), expr_stmt)
for lazy_context in cn.infer().iterate(cn): for lazy_context in cn.infer().iterate(cn):
for context in lazy_context.infer(): for context in lazy_context.infer():
if is_string(context): 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: if abs_path is not None:
yield abs_path yield abs_path
def _execute_code(module_path, code): def _paths_from_list_modifications(module_context, trailer1, trailer2):
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):
""" extract the path from either "sys.path.append" or "sys.path.insert" """ """ 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 # Guarantee that both are trailers, the first one a name and the second one
# a function execution with at least one param. # a function execution with at least one param.
if not (trailer1.type == 'trailer' and trailer1.children[0] == '.' if not (trailer1.type == 'trailer' and trailer1.children[0] == '.'
and trailer2.type == 'trailer' and trailer2.children[0] == '(' and trailer2.type == 'trailer' and trailer2.children[0] == '('
and len(trailer2.children) == 3): and len(trailer2.children) == 3):
return [] return
name = trailer1.children[1].value name = trailer1.children[1].value
if name not in ['insert', 'append']: if name not in ['insert', 'append']:
return [] return
arg = trailer2.children[1] arg = trailer2.children[1]
if name == 'insert' and len(arg.children) in (3, 4): # Possible trailing comma. if name == 'insert' and len(arg.children) in (3, 4): # Possible trailing comma.
arg = arg.children[2] 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): def check_sys_path_modifications(module_context):
@@ -190,7 +179,7 @@ def check_sys_path_modifications(module_context):
if len(power.children) >= 4: if len(power.children) >= 4:
added.extend( added.extend(
_paths_from_list_modifications( _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': 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'))) sys.path.append(dirname(os.path.abspath('thirdparty' + os.path.sep + 'asdf')))
# modifications, that should fail: # modifications, that should fail:
# because of sys module
sys.path.append(sys.path[1] + '/thirdparty')
# syntax err # syntax err
sys.path.append('a' +* '/thirdparty') sys.path.append('a' +* '/thirdparty')
@@ -18,8 +16,9 @@ import evaluate
#? ['evaluator_function_cache'] #? ['evaluator_function_cache']
evaluate.Evaluator_fu evaluate.Evaluator_fu
#? ['jedi_'] # Those don't work because dirname and abspath are not properly understood.
##? ['jedi_']
import jedi_ import jedi_
#? ['el'] ##? ['el']
jedi_.el jedi_.el

View File

@@ -4,10 +4,18 @@ Tests of ``jedi.api.Interpreter``.
import pytest import pytest
import jedi 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 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 _GlobalNameSpace():
class SideEffectContainer(): class SideEffectContainer():
pass pass
@@ -247,7 +255,7 @@ def test_completion_param_annotations():
# Need to define this function not directly in Python. Otherwise Jedi is to # Need to define this function not directly in Python. Otherwise Jedi is to
# clever and uses the Python code instead of the signature object. # clever and uses the Python code instead of the signature object.
code = 'def foo(a: 1, b: str, c: int = 1.0): pass' code = 'def foo(a: 1, b: str, c: int = 1.0): pass'
exec_function(code, locals()) exec_(code, locals())
script = jedi.Interpreter('foo', [locals()]) script = jedi.Interpreter('foo', [locals()])
c, = script.completions() c, = script.completions()
a, b, c = c.params a, b, c = c.params