1"""A sandbox layer that ensures unsafe operations cannot be performed.
2Useful when the template itself comes from an untrusted source.
3"""
4import operator
5import types
6from _string import formatter_field_name_split
7from collections import abc
8from collections import deque
9from string import Formatter
10
11from markupsafe import EscapeFormatter
12from markupsafe import Markup
13
14from .environment import Environment
15from .exceptions import SecurityError
16
17#: maximum number of items a range may produce
18MAX_RANGE = 100000
19
20#: Unsafe function attributes.
21UNSAFE_FUNCTION_ATTRIBUTES = set()
22
23#: Unsafe method attributes. Function attributes are unsafe for methods too.
24UNSAFE_METHOD_ATTRIBUTES = set()
25
26#: unsafe generator attributes.
27UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
28
29#: unsafe attributes on coroutines
30UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
31
32#: unsafe attributes on async generators
33UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
34
35_mutable_spec = (
36    (
37        abc.MutableSet,
38        frozenset(
39            [
40                "add",
41                "clear",
42                "difference_update",
43                "discard",
44                "pop",
45                "remove",
46                "symmetric_difference_update",
47                "update",
48            ]
49        ),
50    ),
51    (
52        abc.MutableMapping,
53        frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
54    ),
55    (
56        abc.MutableSequence,
57        frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]),
58    ),
59    (
60        deque,
61        frozenset(
62            [
63                "append",
64                "appendleft",
65                "clear",
66                "extend",
67                "extendleft",
68                "pop",
69                "popleft",
70                "remove",
71                "rotate",
72            ]
73        ),
74    ),
75)
76
77
78def inspect_format_method(callable):
79    if not isinstance(
80        callable, (types.MethodType, types.BuiltinMethodType)
81    ) or callable.__name__ not in ("format", "format_map"):
82        return None
83    obj = callable.__self__
84    if isinstance(obj, str):
85        return obj
86
87
88def safe_range(*args):
89    """A range that can't generate ranges with a length of more than
90    MAX_RANGE items.
91    """
92    rng = range(*args)
93
94    if len(rng) > MAX_RANGE:
95        raise OverflowError(
96            "Range too big. The sandbox blocks ranges larger than"
97            f" MAX_RANGE ({MAX_RANGE})."
98        )
99
100    return rng
101
102
103def unsafe(f):
104    """Marks a function or method as unsafe.
105
106    .. code-block: python
107
108        @unsafe
109        def delete(self):
110            pass
111    """
112    f.unsafe_callable = True
113    return f
114
115
116def is_internal_attribute(obj, attr):
117    """Test if the attribute given is an internal python attribute.  For
118    example this function returns `True` for the `func_code` attribute of
119    python objects.  This is useful if the environment method
120    :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
121
122    >>> from jinja2.sandbox import is_internal_attribute
123    >>> is_internal_attribute(str, "mro")
124    True
125    >>> is_internal_attribute(str, "upper")
126    False
127    """
128    if isinstance(obj, types.FunctionType):
129        if attr in UNSAFE_FUNCTION_ATTRIBUTES:
130            return True
131    elif isinstance(obj, types.MethodType):
132        if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES:
133            return True
134    elif isinstance(obj, type):
135        if attr == "mro":
136            return True
137    elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
138        return True
139    elif isinstance(obj, types.GeneratorType):
140        if attr in UNSAFE_GENERATOR_ATTRIBUTES:
141            return True
142    elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType):
143        if attr in UNSAFE_COROUTINE_ATTRIBUTES:
144            return True
145    elif hasattr(types, "AsyncGeneratorType") and isinstance(
146        obj, types.AsyncGeneratorType
147    ):
148        if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
149            return True
150    return attr.startswith("__")
151
152
153def modifies_known_mutable(obj, attr):
154    """This function checks if an attribute on a builtin mutable object
155    (list, dict, set or deque) or the corresponding ABCs would modify it
156    if called.
157
158    >>> modifies_known_mutable({}, "clear")
159    True
160    >>> modifies_known_mutable({}, "keys")
161    False
162    >>> modifies_known_mutable([], "append")
163    True
164    >>> modifies_known_mutable([], "index")
165    False
166
167    If called with an unsupported object, ``False`` is returned.
168
169    >>> modifies_known_mutable("foo", "upper")
170    False
171    """
172    for typespec, unsafe in _mutable_spec:
173        if isinstance(obj, typespec):
174            return attr in unsafe
175    return False
176
177
178class SandboxedEnvironment(Environment):
179    """The sandboxed environment.  It works like the regular environment but
180    tells the compiler to generate sandboxed code.  Additionally subclasses of
181    this environment may override the methods that tell the runtime what
182    attributes or functions are safe to access.
183
184    If the template tries to access insecure code a :exc:`SecurityError` is
185    raised.  However also other exceptions may occur during the rendering so
186    the caller has to ensure that all exceptions are caught.
187    """
188
189    sandboxed = True
190
191    #: default callback table for the binary operators.  A copy of this is
192    #: available on each instance of a sandboxed environment as
193    #: :attr:`binop_table`
194    default_binop_table = {
195        "+": operator.add,
196        "-": operator.sub,
197        "*": operator.mul,
198        "/": operator.truediv,
199        "//": operator.floordiv,
200        "**": operator.pow,
201        "%": operator.mod,
202    }
203
204    #: default callback table for the unary operators.  A copy of this is
205    #: available on each instance of a sandboxed environment as
206    #: :attr:`unop_table`
207    default_unop_table = {"+": operator.pos, "-": operator.neg}
208
209    #: a set of binary operators that should be intercepted.  Each operator
210    #: that is added to this set (empty by default) is delegated to the
211    #: :meth:`call_binop` method that will perform the operator.  The default
212    #: operator callback is specified by :attr:`binop_table`.
213    #:
214    #: The following binary operators are interceptable:
215    #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
216    #:
217    #: The default operation form the operator table corresponds to the
218    #: builtin function.  Intercepted calls are always slower than the native
219    #: operator call, so make sure only to intercept the ones you are
220    #: interested in.
221    #:
222    #: .. versionadded:: 2.6
223    intercepted_binops = frozenset()
224
225    #: a set of unary operators that should be intercepted.  Each operator
226    #: that is added to this set (empty by default) is delegated to the
227    #: :meth:`call_unop` method that will perform the operator.  The default
228    #: operator callback is specified by :attr:`unop_table`.
229    #:
230    #: The following unary operators are interceptable: ``+``, ``-``
231    #:
232    #: The default operation form the operator table corresponds to the
233    #: builtin function.  Intercepted calls are always slower than the native
234    #: operator call, so make sure only to intercept the ones you are
235    #: interested in.
236    #:
237    #: .. versionadded:: 2.6
238    intercepted_unops = frozenset()
239
240    def intercept_unop(self, operator):
241        """Called during template compilation with the name of a unary
242        operator to check if it should be intercepted at runtime.  If this
243        method returns `True`, :meth:`call_unop` is executed for this unary
244        operator.  The default implementation of :meth:`call_unop` will use
245        the :attr:`unop_table` dictionary to perform the operator with the
246        same logic as the builtin one.
247
248        The following unary operators are interceptable: ``+`` and ``-``
249
250        Intercepted calls are always slower than the native operator call,
251        so make sure only to intercept the ones you are interested in.
252
253        .. versionadded:: 2.6
254        """
255        return False
256
257    def __init__(self, *args, **kwargs):
258        Environment.__init__(self, *args, **kwargs)
259        self.globals["range"] = safe_range
260        self.binop_table = self.default_binop_table.copy()
261        self.unop_table = self.default_unop_table.copy()
262
263    def is_safe_attribute(self, obj, attr, value):
264        """The sandboxed environment will call this method to check if the
265        attribute of an object is safe to access.  Per default all attributes
266        starting with an underscore are considered private as well as the
267        special attributes of internal python objects as returned by the
268        :func:`is_internal_attribute` function.
269        """
270        return not (attr.startswith("_") or is_internal_attribute(obj, attr))
271
272    def is_safe_callable(self, obj):
273        """Check if an object is safely callable.  Per default a function is
274        considered safe unless the `unsafe_callable` attribute exists and is
275        True.  Override this method to alter the behavior, but this won't
276        affect the `unsafe` decorator from this module.
277        """
278        return not (
279            getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
280        )
281
282    def call_binop(self, context, operator, left, right):
283        """For intercepted binary operator calls (:meth:`intercepted_binops`)
284        this function is executed instead of the builtin operator.  This can
285        be used to fine tune the behavior of certain operators.
286
287        .. versionadded:: 2.6
288        """
289        return self.binop_table[operator](left, right)
290
291    def call_unop(self, context, operator, arg):
292        """For intercepted unary operator calls (:meth:`intercepted_unops`)
293        this function is executed instead of the builtin operator.  This can
294        be used to fine tune the behavior of certain operators.
295
296        .. versionadded:: 2.6
297        """
298        return self.unop_table[operator](arg)
299
300    def getitem(self, obj, argument):
301        """Subscribe an object from sandboxed code."""
302        try:
303            return obj[argument]
304        except (TypeError, LookupError):
305            if isinstance(argument, str):
306                try:
307                    attr = str(argument)
308                except Exception:
309                    pass
310                else:
311                    try:
312                        value = getattr(obj, attr)
313                    except AttributeError:
314                        pass
315                    else:
316                        if self.is_safe_attribute(obj, argument, value):
317                            return value
318                        return self.unsafe_undefined(obj, argument)
319        return self.undefined(obj=obj, name=argument)
320
321    def getattr(self, obj, attribute):
322        """Subscribe an object from sandboxed code and prefer the
323        attribute.  The attribute passed *must* be a bytestring.
324        """
325        try:
326            value = getattr(obj, attribute)
327        except AttributeError:
328            try:
329                return obj[attribute]
330            except (TypeError, LookupError):
331                pass
332        else:
333            if self.is_safe_attribute(obj, attribute, value):
334                return value
335            return self.unsafe_undefined(obj, attribute)
336        return self.undefined(obj=obj, name=attribute)
337
338    def unsafe_undefined(self, obj, attribute):
339        """Return an undefined object for unsafe attributes."""
340        return self.undefined(
341            f"access to attribute {attribute!r} of"
342            f" {obj.__class__.__name__!r} object is unsafe.",
343            name=attribute,
344            obj=obj,
345            exc=SecurityError,
346        )
347
348    def format_string(self, s, args, kwargs, format_func=None):
349        """If a format call is detected, then this is routed through this
350        method so that our safety sandbox can be used for it.
351        """
352        if isinstance(s, Markup):
353            formatter = SandboxedEscapeFormatter(self, s.escape)
354        else:
355            formatter = SandboxedFormatter(self)
356
357        if format_func is not None and format_func.__name__ == "format_map":
358            if len(args) != 1 or kwargs:
359                raise TypeError(
360                    "format_map() takes exactly one argument"
361                    f" {len(args) + (kwargs is not None)} given"
362                )
363
364            kwargs = args[0]
365            args = None
366
367        rv = formatter.vformat(s, args, kwargs)
368        return type(s)(rv)
369
370    def call(__self, __context, __obj, *args, **kwargs):  # noqa: B902
371        """Call an object from sandboxed code."""
372        fmt = inspect_format_method(__obj)
373        if fmt is not None:
374            return __self.format_string(fmt, args, kwargs, __obj)
375
376        # the double prefixes are to avoid double keyword argument
377        # errors when proxying the call.
378        if not __self.is_safe_callable(__obj):
379            raise SecurityError(f"{__obj!r} is not safely callable")
380        return __context.call(__obj, *args, **kwargs)
381
382
383class ImmutableSandboxedEnvironment(SandboxedEnvironment):
384    """Works exactly like the regular `SandboxedEnvironment` but does not
385    permit modifications on the builtin mutable objects `list`, `set`, and
386    `dict` by using the :func:`modifies_known_mutable` function.
387    """
388
389    def is_safe_attribute(self, obj, attr, value):
390        if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
391            return False
392        return not modifies_known_mutable(obj, attr)
393
394
395class SandboxedFormatterMixin:
396    def __init__(self, env):
397        self._env = env
398
399    def get_field(self, field_name, args, kwargs):
400        first, rest = formatter_field_name_split(field_name)
401        obj = self.get_value(first, args, kwargs)
402        for is_attr, i in rest:
403            if is_attr:
404                obj = self._env.getattr(obj, i)
405            else:
406                obj = self._env.getitem(obj, i)
407        return obj, first
408
409
410class SandboxedFormatter(SandboxedFormatterMixin, Formatter):
411    def __init__(self, env):
412        SandboxedFormatterMixin.__init__(self, env)
413        Formatter.__init__(self)
414
415
416class SandboxedEscapeFormatter(SandboxedFormatterMixin, EscapeFormatter):
417    def __init__(self, env, escape):
418        SandboxedFormatterMixin.__init__(self, env)
419        EscapeFormatter.__init__(self, escape)
420