From 8254c266f962d5febe46396d5083bb9c1da74840 Mon Sep 17 00:00:00 2001
From: Brian Coca <brian.coca+git@gmail.com>
Date: Tue, 4 Jun 2019 08:43:15 -0400
Subject: [PATCH] just dont pass locals

 - also fix globals
 - added tests
---
 changelogs/fragments/fix_safe_eval.yml        |  2 +
 lib/ansible/template/__init__.py              |  2 +-
 lib/ansible/template/safe_eval.py             |  8 ++-
 .../targets/template/corner_cases.yml         | 51 +++++++++++++++++++
 test/integration/targets/template/runme.sh    |  3 ++
 5 files changed, 63 insertions(+), 3 deletions(-)
 create mode 100644 changelogs/fragments/fix_safe_eval.yml
 create mode 100644 test/integration/targets/template/corner_cases.yml

diff --git a/changelogs/fragments/fix_safe_eval.yml b/changelogs/fragments/fix_safe_eval.yml
new file mode 100644
index 0000000..19220b3
--- /dev/null
+++ b/changelogs/fragments/fix_safe_eval.yml
@@ -0,0 +1,2 @@
+bugfixes:
+    - Handle improper variable substitution that was happening in safe_eval, it was always meant to just do 'type enforcement' and have Jinja2 deal with all variable interpolation. Also see CVE-2019-10156
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
index 8a0d569..140891c 100644
--- a/lib/ansible/template/__init__.py
+++ b/lib/ansible/template/__init__.py
@@ -486,7 +486,7 @@ class Templar:
                             # if this looks like a dictionary or list, convert it to such using the safe_eval method
                             if (result.startswith("{") and not result.startswith(self.environment.variable_start_string)) or \
                                     result.startswith("[") or result in ("True", "False"):
-                                eval_results = safe_eval(result, locals=self._available_variables, include_exceptions=True)
+                                eval_results = safe_eval(result, include_exceptions=True)
                                 if eval_results[1] is None:
                                     result = eval_results[0]
                                     if unsafe:
diff --git a/lib/ansible/template/safe_eval.py b/lib/ansible/template/safe_eval.py
index a142d1d..c2d161d 100644
--- a/lib/ansible/template/safe_eval.py
+++ b/lib/ansible/template/safe_eval.py
@@ -42,10 +42,14 @@ def safe_eval(expr, locals=None, include_exceptions=False):
 
     # define certain JSON types
     # eg. JSON booleans are unknown to python eval()
-    JSON_TYPES = {
+    OUR_GLOBALS = {
+        '__builtins__': {},  # avoid global builtins as per eval docs
         'false': False,
         'null': None,
         'true': True,
+        # also add back some builtins we do need
+        'True': True,
+        'False': False,
     }
 
     # this is the whitelist of AST nodes we are going to
@@ -130,7 +134,7 @@ def safe_eval(expr, locals=None, include_exceptions=False):
         # Note: passing our own globals and locals here constrains what
         # callables (and other identifiers) are recognized.  this is in
         # addition to the filtering of builtins done in CleansingNodeVisitor
-        result = eval(compiled, JSON_TYPES, dict(locals))
+        result = eval(compiled, OUR_GLOBALS, dict(locals))
 
         if include_exceptions:
             return (result, None)
diff --git a/test/integration/targets/template/corner_cases.yml b/test/integration/targets/template/corner_cases.yml
new file mode 100644
index 0000000..48782f7
--- /dev/null
+++ b/test/integration/targets/template/corner_cases.yml
@@ -0,0 +1,51 @@
+- name: test tempating corner cases
+  hosts: localhost
+  gather_facts: false
+  vars:
+    empty_list: []
+    dont: I SHOULD NOT BE TEMPLATED
+    other: I WORK
+  tasks:
+    - name: 'ensure we are not interpolating data from outside of j2 delmiters'
+      assert:
+        that:
+            - '"I SHOULD NOT BE TEMPLATED" not in adjacent'
+            - globals1 == "[[], globals()]"
+            - globals2 == "[[], globals]"
+      vars:
+        adjacent: "{{ empty_list }} + [dont]"
+        globals1: "[{{ empty_list }}, globals()]"
+        globals2: "[{{ empty_list }}, globals]"
+
+    - name: 'ensure we can add lists'
+      assert:
+        that:
+            - (empty_list + [other]) == [other]
+            - (empty_list + [other, other]) == [other, other]
+            - (dont_exist|default([]) + [other]) == [other]
+            - ([other] + [empty_list, other]) == [other, [], other]
+
+    - name: 'ensure comments go away and we still dont interpolate in string'
+      assert:
+        that:
+            - 'comm1 == " + [dont]"'
+            - 'comm2 == " #} + [dont]"'
+      vars:
+        comm1: '{# {{nothing}} {# #} + [dont]'
+        comm2: "{# {{nothing}} {# #} #} + [dont]"
+
+    - name: test additions with facts, set them up
+      set_fact:
+        inames: []
+        iname: "{{ prefix ~ '-options' }}"
+        iname_1: "{{ prefix ~ '-options-1' }}"
+      vars:
+        prefix: 'bo'
+
+    - name: add the facts
+      set_fact:
+        inames: '{{ inames + [iname, iname_1] }}'
+
+    - assert:
+        that:
+            - inames == ['bo-options', 'bo-options-1']
diff --git a/test/integration/targets/template/runme.sh b/test/integration/targets/template/runme.sh
index 4df69c4..5961895 100755
--- a/test/integration/targets/template/runme.sh
+++ b/test/integration/targets/template/runme.sh
@@ -9,3 +9,6 @@ ansible testhost -i testhost, -m debug -a 'msg={{ hostvars["localhost"] }}' -e "
 
 # Test for https://github.com/ansible/ansible/issues/27262
 ansible-playbook ansible_managed.yml -c  ansible_managed.cfg -i ../../inventory -e @../../integration_config.yml -v "$@"
+
+# Test for #42585
+NSIBLE_ROLES_PATH=../ ansible-playbook custom_template.yml -i ../../inventory -e @../../integration_config.yml -v "$@"
-- 
2.27.0

