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