From cc4634a5e73c06c6b4581f11171289ca9228391e Mon Sep 17 00:00:00 2001
From: James Cammarata <jimi@sngx.net>
Date: Tue, 10 Jan 2017 16:54:00 -0600
Subject: [PATCH] Additional fixes for security related to CVE-2016-9587

(cherry picked from commit d316068831f9e08ef96833200ec7df2132263966)
---
 lib/ansible/playbook/conditional.py | 10 +++++-----
 lib/ansible/template/__init__.py    | 28 ++++++++++++++--------------
 2 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/lib/ansible/playbook/conditional.py b/lib/ansible/playbook/conditional.py
index 99e377c4db1..57e20a0159e 100644
--- a/lib/ansible/playbook/conditional.py
+++ b/lib/ansible/playbook/conditional.py
@@ -104,7 +104,7 @@ class Conditional:
         if conditional is None or conditional == '':
             return True
 
-        if conditional in all_vars and '-' not in text_type(all_vars[conditional]):
+        if conditional in all_vars and re.match("^[_A-Za-z][_a-zA-Z0-9]*$", conditional):
             conditional = all_vars[conditional]
 
         # make sure the templar is using the variables specified with this method
@@ -116,12 +116,12 @@ class Conditional:
                 return conditional
 
             # a Jinja2 evaluation that results in something Python can eval!
-            if hasattr(conditional, '__UNSAFE__') and LOOKUP_REGEX.match(conditional):
-                raise AnsibleError("The conditional '%s' contains variables which came from an unsafe " \
-                                   "source and also contains a lookup() call, failing conditional check" % conditional)
+            disable_lookups = False
+            if hasattr(conditional, '__UNSAFE__'):
+                disable_lookups = True
 
             presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional
-            val = templar.template(presented).strip()
+            val = templar.template(presented, disable_lookups=disable_lookups).strip()
             if val == "True":
                 return True
             elif val == "False":
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
index 53b267543f2..1a43486f9e7 100644
--- a/lib/ansible/template/__init__.py
+++ b/lib/ansible/template/__init__.py
@@ -30,10 +30,8 @@ from numbers import Number
 from jinja2 import Environment
 from jinja2.loaders import FileSystemLoader
 from jinja2.exceptions import TemplateSyntaxError, UndefinedError
-from jinja2.nodes import EvalContext
 from jinja2.utils import concat as j2_concat
 from jinja2.runtime import Context, StrictUndefined
-
 from ansible import constants as C
 from ansible.compat.six import string_types, text_type
 from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleUndefinedVariable
@@ -42,7 +40,6 @@ from ansible.template.safe_eval import safe_eval
 from ansible.template.template import AnsibleJ2Template
 from ansible.template.vars import AnsibleJ2Vars
 from ansible.module_utils._text import to_native, to_text
-from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var
 
 try:
     from hashlib import sha1
@@ -127,13 +124,6 @@ def _count_newlines_from_end(in_str):
         # Uncommon cases: zero length string and string containing only newlines
         return i
 
-class AnsibleEvalContext(EvalContext):
-    '''
-    A custom jinja2 EvalContext, which is currently unused and saved
-    here for possible future use.
-    '''
-    pass
-
 class AnsibleContext(Context):
     '''
     A custom context, which intercepts resolve() calls and sets a flag
@@ -143,7 +133,6 @@ class AnsibleContext(Context):
     '''
     def __init__(self, *args, **kwargs):
         super(AnsibleContext, self).__init__(*args, **kwargs)
-        self.eval_ctx = AnsibleEvalContext(self.environment, self.name)
         self.unsafe = False
 
     def _is_unsafe(self, val):
@@ -335,7 +324,7 @@ class Templar:
         self._available_variables = variables
         self._cached_result       = {}
 
-    def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, convert_data=True, static_vars = [''], cache = True, bare_deprecated=True):
+    def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, convert_data=True, static_vars = [''], cache = True, bare_deprecated=True, disable_lookups=False):
         '''
         Templates (possibly recursively) any given data as input. If convert_bare is
         set to True, the given data will be wrapped as a jinja2 variable ('{{foo}}')
@@ -391,6 +380,7 @@ class Templar:
                             escape_backslashes=escape_backslashes,
                             fail_on_undefined=fail_on_undefined,
                             overrides=overrides,
+                            disable_lookups=disable_lookups,
                         )
                         unsafe = hasattr(result, '__UNSAFE__')
                         if convert_data and not self._no_type_regex.match(variable) and not unsafe:
@@ -401,6 +391,7 @@ class Templar:
                                 if eval_results[1] is None:
                                     result = eval_results[0]
                                     if unsafe:
+                                        from ansible.vars.unsafe_proxy import wrap_var
                                         result = wrap_var(result)
                                 else:
                                     # FIXME: if the safe_eval raised an error, should we do something with it?
@@ -482,6 +473,9 @@ class Templar:
         '''
         return thing if thing is not None else ''
 
+    def _fail_lookup(self, name, *args, **kwargs):
+        raise AnsibleError("The lookup `%s` was found, however lookups were disabled from templating" % name)
+
     def _lookup(self, name, *args, **kwargs):
         instance = self._lookup_loader.get(name.lower(), loader=self._loader, templar=self)
 
@@ -501,6 +495,7 @@ class Templar:
                 ran = None
 
             if ran:
+                from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var
                 if wantlist:
                     ran = wrap_var(ran)
                 else:
@@ -516,7 +511,7 @@ class Templar:
         else:
             raise AnsibleError("lookup plugin (%s) not found" % name)
 
-    def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None):
+    def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, disable_lookups=False):
         # For preserving the number of input newlines in the output (used
         # later in this method)
         data_newlines = _count_newlines_from_end(data)
@@ -560,7 +555,11 @@ class Templar:
                 else:
                     return data
 
-            t.globals['lookup']   = self._lookup
+            if disable_lookups:
+                t.globals['lookup'] = self._fail_lookup
+            else:
+                t.globals['lookup'] = self._lookup
+
             t.globals['finalize'] = self._finalize
 
             jvars = AnsibleJ2Vars(self, t.globals)
@@ -571,6 +570,7 @@ class Templar:
             try:
                 res = j2_concat(rf)
                 if new_context.unsafe:
+                    from ansible.vars.unsafe_proxy import wrap_var
                     res = wrap_var(res)
             except TypeError as te:
                 if 'StrictUndefined' in to_native(te):