Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cloudinit/handlers/jinja_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
JUndefinedError: Type[Exception]
try:
from jinja2.exceptions import UndefinedError as JUndefinedError
from jinja2.exceptions import SecurityError as JSecurityError
from jinja2.lexer import operator_re
except ImportError:
# No jinja2 dependency
JUndefinedError = Exception
JSecurityError = Exception
operator_re = re.compile(r"[-.]")

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -147,7 +149,7 @@ def render_jinja_payload(payload, payload_fn, instance_data, debug=False):
)
try:
rendered_payload = render_string(payload, instance_jinja_vars)
except (TypeError, JUndefinedError) as e:
except (TypeError, JUndefinedError, JSecurityError) as e:
LOG.warning("Ignoring jinja template for %s: %s", payload_fn, str(e))
return None
warnings = [
Expand Down
14 changes: 7 additions & 7 deletions cloudinit/templater.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
JUndefined: Any
try:
from jinja2 import DebugUndefined as _DebugUndefined
from jinja2 import Template as JTemplate
from jinja2.sandbox import SandboxedEnvironment as JSandboxedEnvironment

JINJA_AVAILABLE = True
JUndefined = _DebugUndefined
Expand Down Expand Up @@ -149,13 +149,13 @@ def jinja_render(content, params):
add = "\n" if content.endswith("\n") else ""
try:
with performance.Timed("Rendering jinja2 template"):
jinja_env = JSandboxedEnvironment(
undefined=UndefinedJinjaVariable,
trim_blocks=True,
extensions=["jinja2.ext.do"],
)
return (
JTemplate(
content,
undefined=UndefinedJinjaVariable,
trim_blocks=True,
extensions=["jinja2.ext.do"],
).render(**params)
jinja_env.from_string(content).render(**params)
+ add
)
except TemplateSyntaxError as template_syntax_error:
Expand Down
20 changes: 20 additions & 0 deletions tests/unittests/test_builtin_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,26 @@ def test_render_jinja_payload_replaces_missing_variables_and_warns(
)
assert expected_log in caplog.text

@skipUnlessJinja()
def test_render_jinja_payload_blocks_unsafe_attribute_access(
self, caplog
):
payload = (
"## template: jinja\n"
"{{ ''.__class__.__mro__[1].__subclasses__()[:3] }}"
)

assert (
render_jinja_payload(
payload=payload,
payload_fn="myfile",
instance_data={"v1": {"hostname": "foo"}},
)
is None
)
assert "Ignoring jinja template for myfile" in caplog.text
assert "__class__" in caplog.text


class TestShellScriptByFrequencyHandlers:
@pytest.fixture(autouse=True)
Expand Down
11 changes: 11 additions & 0 deletions tests/unittests/test_templating.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from unittest import mock

import pytest
from jinja2.exceptions import SecurityError

from cloudinit import templater
from cloudinit.templater import JinjaSyntaxParsingException
Expand Down Expand Up @@ -171,6 +172,16 @@ def test_jinja_do_extension_render_to_string(self):
== expected_result
)

@test_helpers.skipUnlessJinja()
def test_jinja_blocks_unsafe_attribute_access(self):
template = self.add_header(
"jinja",
"{{ ''.__class__.__mro__[1].__subclasses__()[:3] }}",
)

with pytest.raises(SecurityError):
templater.render_string(template, {})


class TestJinjaSyntaxParsingException:
def test_jinja_syntax_parsing_exception_message(self):
Expand Down