diff --git a/CVE-2019-3828.patch b/CVE-2019-3828.patch new file mode 100644 index 0000000..92e830f --- /dev/null +++ b/CVE-2019-3828.patch @@ -0,0 +1,150 @@ +From f3edc091523fbe301926b7a0db25fbbd96940d93 Mon Sep 17 00:00:00 2001 +From: Matt Martz +Date: Mon, 18 Feb 2019 10:45:05 -0600 +Subject: [PATCH] [stable-2.5] Disallow use of remote home directories + containing .. in their path (CVE-2019-3828) (#52133) (#52175) + +* Disallow use of remote home directories containing .. in their path + +* Add CVE to changelog +(cherry picked from commit b34d141) + +Co-authored-by: Matt Martz +--- + .../fragments/disallow-relative-homedir.yaml | 3 + + lib/ansible/plugins/action/__init__.py | 3 + + test/units/plugins/action/test_action.py | 61 ++++++++++++------- + 3 files changed, 44 insertions(+), 23 deletions(-) + create mode 100644 changelogs/fragments/disallow-relative-homedir.yaml + +diff --git a/changelogs/fragments/disallow-relative-homedir.yaml b/changelogs/fragments/disallow-relative-homedir.yaml +new file mode 100644 +index 00000000000000..0ae36ef94d27fd +--- /dev/null ++++ b/changelogs/fragments/disallow-relative-homedir.yaml +@@ -0,0 +1,3 @@ ++bugfixes: ++- remote home directory - Disallow use of remote home directories that include ++ relative pathing by means of `..` (CVE-2019-3828) (https://github.com/ansible/ansible/pull/52133) +diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py +index ba8f32e52983f8..23d95ee444bc23 100644 +--- a/lib/ansible/plugins/action/__init__.py ++++ b/lib/ansible/plugins/action/__init__.py +@@ -609,6 +609,9 @@ def _remote_expand_user(self, path, sudoable=True, pathsep=None): + else: + expanded = initial_fragment + ++ if '..' in os.path.dirname(expanded).split('/'): ++ raise AnsibleError("'%s' returned an invalid relative home directory path containing '..'" % self._play_context.remote_addr) ++ + return expanded + + def _strip_success_message(self, data): +diff --git a/test/units/plugins/action/test_action.py b/test/units/plugins/action/test_action.py +index 52fa3bea1bece4..5221e54ed4fafb 100644 +--- a/test/units/plugins/action/test_action.py ++++ b/test/units/plugins/action/test_action.py +@@ -56,6 +56,28 @@ + """ + + ++def _action_base(): ++ fake_loader = DictDataLoader({ ++ }) ++ mock_module_loader = MagicMock() ++ mock_shared_loader_obj = MagicMock() ++ mock_shared_loader_obj.module_loader = mock_module_loader ++ mock_connection_loader = MagicMock() ++ ++ mock_shared_loader_obj.connection_loader = mock_connection_loader ++ mock_connection = MagicMock() ++ ++ play_context = MagicMock() ++ ++ action_base = DerivedActionBase(task=None, ++ connection=mock_connection, ++ play_context=play_context, ++ loader=fake_loader, ++ templar=None, ++ shared_loader_obj=mock_shared_loader_obj) ++ return action_base ++ ++ + class DerivedActionBase(ActionBase): + TRANSFERS_FILES = False + +@@ -522,6 +544,18 @@ def test_action_base_sudo_only_if_user_differs(self): + finally: + C.BECOME_ALLOW_SAME_USER = become_allow_same_user + ++ def test__remote_expand_user_relative_pathing(self): ++ action_base = _action_base() ++ action_base._play_context.remote_addr = 'bar' ++ action_base._low_level_execute_command = MagicMock(return_value={'stdout': b'../home/user'}) ++ action_base._connection._shell.join_path.return_value = '../home/user/foo' ++ with self.assertRaises(AnsibleError) as cm: ++ action_base._remote_expand_user('~/foo') ++ self.assertEqual( ++ cm.exception.message, ++ "'bar' returned an invalid relative home directory path containing '..'" ++ ) ++ + + class TestActionBaseCleanReturnedData(unittest.TestCase): + def test(self): +@@ -573,27 +607,8 @@ def fake_all(path_only=None): + + class TestActionBaseParseReturnedData(unittest.TestCase): + +- def _action_base(self): +- fake_loader = DictDataLoader({ +- }) +- mock_module_loader = MagicMock() +- mock_shared_loader_obj = MagicMock() +- mock_shared_loader_obj.module_loader = mock_module_loader +- mock_connection_loader = MagicMock() +- +- mock_shared_loader_obj.connection_loader = mock_connection_loader +- mock_connection = MagicMock() +- +- action_base = DerivedActionBase(task=None, +- connection=mock_connection, +- play_context=None, +- loader=fake_loader, +- templar=None, +- shared_loader_obj=mock_shared_loader_obj) +- return action_base +- + def test_fail_no_json(self): +- action_base = self._action_base() ++ action_base = _action_base() + rc = 0 + stdout = 'foo\nbar\n' + err = 'oopsy' +@@ -607,7 +622,7 @@ def test_fail_no_json(self): + self.assertEqual(res['module_stderr'], err) + + def test_json_empty(self): +- action_base = self._action_base() ++ action_base = _action_base() + rc = 0 + stdout = '{}\n' + err = '' +@@ -621,7 +636,7 @@ def test_json_empty(self): + self.assertFalse(res) + + def test_json_facts(self): +- action_base = self._action_base() ++ action_base = _action_base() + rc = 0 + stdout = '{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}}\n' + err = '' +@@ -637,7 +652,7 @@ def test_json_facts(self): + # self.assertIsInstance(res['ansible_facts'], AnsibleUnsafe) + + def test_json_facts_add_host(self): +- action_base = self._action_base() ++ action_base = _action_base() + rc = 0 + stdout = '''{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}, + "add_host": {"host_vars": {"some_key": ["whatever the add_host object is"]} diff --git a/CVE-2020-10684.patch b/CVE-2020-10684.patch index 4a1d623..8d8a995 100644 --- a/CVE-2020-10684.patch +++ b/CVE-2020-10684.patch @@ -51,10 +51,10 @@ index c83709d..0a4fe07 100644 - # exceptions to 'deprefixing' - deprefixed[k] = deepcopy(facts[k]) + if k.startswith('ansible_') and k not in ('ansible_local',): -+ deprefixed[k[8:]] = module_response_deepcopy(facts[k]) ++ deprefixed[k[8:]] = deepcopy(facts[k]) else: - deprefixed[k.replace('ansible_', '', 1)] = deepcopy(facts[k]) -+ deprefixed[k] = module_response_deepcopy(facts[k]) ++ deprefixed[k] = deepcopy(facts[k]) return {'ansible_facts': deprefixed} diff --git a/test/integration/targets/gathering_facts/library/bogus_facts b/test/integration/targets/gathering_facts/library/bogus_facts diff --git a/CVE-2020-1735.patch b/CVE-2020-1735.patch index 7bdaf49..89f5395 100644 --- a/CVE-2020-1735.patch +++ b/CVE-2020-1735.patch @@ -1,53 +1,100 @@ -From 5292482553dc409081f7f4368398358cbf9f8672 Mon Sep 17 00:00:00 2001 -From: Brian Coca -Date: Wed, 8 Apr 2020 14:28:51 -0400 +From c1dedd38cb6a3ad215cf077c533c93bd978acb93 Mon Sep 17 00:00:00 2001 +From: caodongxia <315816521@qq.com> +Date: Tue, 2 Nov 2021 17:26:43 +0800 Subject: [PATCH] fixed fetch traversal from slurp (#68720) +Reference:https://github.com/ansible/ansible/commit/290bfa820d533dc224e0c3fa7dd7c6b907ed0189.patch -* fixed fetch traversal from slurp - - * ignore slurp result for dest - * fixed naming when source is relative - * fixed bug in local connection plugin - * added tests with fake slurp - * moved existing role tests into runme.sh - * normalized on action excepts - * moved dest transform down to when needed - * added is_subpath check -│ * fixed bug in local connection -fixes #67793 - -CVE-2019-3828 - -(cherry picked from commit ba87c225cd13343c35075fe7fc15b4cf1343fed6) ---- - changelogs/fragments/fetch_no_slurp.yml | 2 ++ - lib/ansible/plugins/action/fetch.py | 23 ++++++--------- - .../fetch/injection/avoid_slurp_return.yml | 26 +++++++++++++++++ + changelogs/fragments/fetch_no_slurp.yml | 2 + + lib/ansible/plugins/action/fetch.py | 44 ++++++++----------- + lib/ansible/utils/path.py | 22 ++++++++++ + test/integration/targets/fetch/aliases | 1 + + .../fetch/injection/avoid_slurp_return.yml | 26 +++++++++++ .../targets/fetch/injection/here.txt | 1 + - .../targets/fetch/injection/library/slurp.py | 29 +++++++++++++++++++ - .../targets/fetch/run_fetch_tests.yml | 5 ++++ - test/integration/targets/fetch/runme.sh | 12 ++++++++ - 7 files changed, 84 insertions(+), 14 deletions(-) + .../targets/fetch/injection/library/slurp.py | 28 ++++++++++++ + .../targets/fetch/run_fetch_tests.yml | 5 +++ + test/integration/targets/fetch/runme.sh | 12 +++++ + 9 files changed, 113 insertions(+), 26 deletions(-) create mode 100644 changelogs/fragments/fetch_no_slurp.yml create mode 100644 test/integration/targets/fetch/injection/avoid_slurp_return.yml create mode 100644 test/integration/targets/fetch/injection/here.txt create mode 100644 test/integration/targets/fetch/injection/library/slurp.py create mode 100644 test/integration/targets/fetch/run_fetch_tests.yml - create mode 100755 test/integration/targets/fetch/runme.sh + create mode 100644 test/integration/targets/fetch/runme.sh diff --git a/changelogs/fragments/fetch_no_slurp.yml b/changelogs/fragments/fetch_no_slurp.yml new file mode 100644 -index 0000000..c742d40 ---- /dev/null +index 0000000000000..c742d40c3ba82 +--- /dev/null +++ b/changelogs/fragments/fetch_no_slurp.yml @@ -0,0 +1,2 @@ +bugfixes: + - In fetch action, avoid using slurp return to set up dest, also ensure no dir traversal CVE-2019-3828. diff --git a/lib/ansible/plugins/action/fetch.py b/lib/ansible/plugins/action/fetch.py -index fbaf992..471732c 100644 +index fbaf992..7db5b43 100644 --- a/lib/ansible/plugins/action/fetch.py +++ b/lib/ansible/plugins/action/fetch.py -@@ -106,12 +106,6 @@ class ActionModule(ActionBase): +@@ -20,13 +20,13 @@ __metaclass__ = type + import os + import base64 + +-from ansible.errors import AnsibleError ++from ansible.errors import AnsibleActionFail, AnsibleActionSkip + from ansible.module_utils._text import to_bytes + from ansible.module_utils.six import string_types + from ansible.module_utils.parsing.convert_bool import boolean + from ansible.plugins.action import ActionBase + from ansible.utils.hashing import checksum, checksum_s, md5, secure_hash +-from ansible.utils.path import makedirs_safe ++from ansible.utils.path import makedirs_safe, is_subpath + + try: + from __main__ import display +@@ -47,24 +47,23 @@ class ActionModule(ActionBase): + + try: + if self._play_context.check_mode: +- result['skipped'] = True +- result['msg'] = 'check mode not (yet) supported for this module' +- return result ++ raise AnsibleActionSkip('check mode not (yet) supported for this module') + + source = self._task.args.get('src', None) +- dest = self._task.args.get('dest', None) ++ original_dest = dest = self._task.args.get('dest', None) + flat = boolean(self._task.args.get('flat'), strict=False) + fail_on_missing = boolean(self._task.args.get('fail_on_missing', True), strict=False) + validate_checksum = boolean(self._task.args.get('validate_checksum', + self._task.args.get('validate_md5', True)), + strict=False) + ++ msg = '' + # validate source and dest are strings FIXME: use basic.py and module specs + if not isinstance(source, string_types): +- result['msg'] = "Invalid type supplied for source option, it must be a string" ++ msg = "Invalid type supplied for source option, it must be a string" + + if not isinstance(dest, string_types): +- result['msg'] = "Invalid type supplied for dest option, it must be a string" ++ msg = "Invalid type supplied for dest option, it must be a string" + + # validate_md5 is the deprecated way to specify validate_checksum + if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args: +@@ -74,11 +73,10 @@ class ActionModule(ActionBase): + display.deprecated('Use validate_checksum instead of validate_md5', version='2.8') + + if source is None or dest is None: +- result['msg'] = "src and dest are required" ++ msg = "src and dest are required" + +- if result.get('msg'): +- result['failed'] = True +- return result ++ if msg: ++ raise AnsibleActionFail(msg) + + source = self._connection._shell.join_path(source) + source = self._remote_expand_user(source) +@@ -106,12 +104,6 @@ class ActionModule(ActionBase): remote_data = base64.b64decode(slurpres['content']) if remote_data is not None: remote_checksum = checksum_s(remote_data) @@ -60,7 +107,7 @@ index fbaf992..471732c 100644 # calculate the destination name if os.path.sep not in self._connection._shell.join_path('a', ''): -@@ -120,13 +114,14 @@ class ActionModule(ActionBase): +@@ -120,13 +112,13 @@ class ActionModule(ActionBase): else: source_local = source @@ -68,8 +115,7 @@ index fbaf992..471732c 100644 + # ensure we only use file name, avoid relative paths + if not is_subpath(dest, original_dest): + # TODO: ? dest = os.path.expanduser(dest.replace(('../',''))) -+ raise AnsibleActionFail("Detected directory traversal, expected to be contained in '%s' but got '%s'" % (original_dest, dest)) -+ ++ raise AnsibleActionFail("Detected directory traversal, expected to be contained in '%s' but got '%s'" %(original_dest, dest)) if flat: if os.path.isdir(to_bytes(dest, errors='surrogate_or_strict')) and not dest.endswith(os.sep): - result['msg'] = "dest is an existing directory, use a trailing slash if you want to fetch src into that directory" @@ -80,7 +126,7 @@ index fbaf992..471732c 100644 if dest.endswith(os.sep): # if the path ends with "/", we'll use the source filename as the # destination filename -@@ -143,8 +138,6 @@ class ActionModule(ActionBase): +@@ -143,8 +135,6 @@ class ActionModule(ActionBase): target_name = self._play_context.remote_addr dest = "%s/%s/%s" % (self._loader.path_dwim(dest), target_name, source_local) @@ -89,16 +135,16 @@ index fbaf992..471732c 100644 if remote_checksum in ('0', '1', '2', '3', '4', '5'): result['changed'] = False result['file'] = source -@@ -172,6 +165,8 @@ class ActionModule(ActionBase): +@@ -172,6 +162,8 @@ class ActionModule(ActionBase): result['msg'] += ", not transferring, ignored" return result -+ dest = os.path.normpath(dest) ++ dest = os.path.normpath(dest) + # calculate checksum for the local file local_checksum = checksum(dest) -@@ -188,7 +183,7 @@ class ActionModule(ActionBase): +@@ -188,7 +180,7 @@ class ActionModule(ActionBase): f.write(remote_data) f.close() except (IOError, OSError) as e: @@ -107,9 +153,46 @@ index fbaf992..471732c 100644 new_checksum = secure_hash(dest) # For backwards compatibility. We'll return None on FIPS enabled systems try: +diff --git a/lib/ansible/utils/path.py b/lib/ansible/utils/path.py +index 717cef6..2ef1316 100644 +--- a/lib/ansible/utils/path.py ++++ b/lib/ansible/utils/path.py +@@ -97,3 +97,25 @@ def basedir(source): + dname = os.path.abspath(dname) + + return to_text(dname, errors='surrogate_or_strict') ++ ++def is_subpath(child, parent): ++ """ ++ Compares paths to check if one is contained in the other ++ :arg: child: Path to test ++ :arg parent; Path to test against ++ """ ++ test = False ++ ++ abs_child = unfrackpath(child, follow=False) ++ abs_parent = unfrackpath(parent, follow=False) ++ ++ c = abs_child.split(os.path.sep) ++ p = abs_parent.split(os.path.sep) ++ ++ try: ++ test = c[:len(p)] == p ++ except IndexError: ++ # child is shorter than parent so cannot be subpath ++ pass ++ ++ return test +diff --git a/test/integration/targets/fetch/aliases b/test/integration/targets/fetch/aliases +index 7af8b7f..197794b 100644 +--- a/test/integration/targets/fetch/aliases ++++ b/test/integration/targets/fetch/aliases +@@ -1 +1,2 @@ + posix/ci/group2 ++needs/target/setup_remote_tmp_dir diff --git a/test/integration/targets/fetch/injection/avoid_slurp_return.yml b/test/integration/targets/fetch/injection/avoid_slurp_return.yml new file mode 100644 -index 0000000..af62dcf +index 0000000..c44b85e --- /dev/null +++ b/test/integration/targets/fetch/injection/avoid_slurp_return.yml @@ -0,0 +1,26 @@ @@ -148,10 +231,10 @@ index 0000000..493021b +this is a test file diff --git a/test/integration/targets/fetch/injection/library/slurp.py b/test/integration/targets/fetch/injection/library/slurp.py new file mode 100644 -index 0000000..7b78ba1 +index 0000000..3916ae8 --- /dev/null +++ b/test/integration/targets/fetch/injection/library/slurp.py -@@ -0,0 +1,29 @@ +@@ -0,0 +1,28 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type @@ -171,7 +254,6 @@ index 0000000..7b78ba1 + +import json +import random -+ +bad_responses = ['../foo', '../../foo', '../../../foo', '/../../../foo', '/../foo', '//..//foo', '..//..//foo'] + + @@ -193,8 +275,8 @@ index 0000000..f2ff1df + roles: + - fetch_tests diff --git a/test/integration/targets/fetch/runme.sh b/test/integration/targets/fetch/runme.sh -new file mode 100755 -index 0000000..7e909dd +new file mode 100644 +index 0000000..3d5b75c --- /dev/null +++ b/test/integration/targets/fetch/runme.sh @@ -0,0 +1,12 @@ diff --git a/ansible-2.5.5-openEuler-hostname.patch b/ansible-2.5.5-openEuler-hostname.patch new file mode 100644 index 0000000..35541f5 --- /dev/null +++ b/ansible-2.5.5-openEuler-hostname.patch @@ -0,0 +1,38 @@ +diff --color -Nur ansible-2.5.5.orig/lib/ansible/modules/system/hostname.py ansible-2.5.5/lib/ansible/modules/system/hostname.py +--- ansible-2.5.5.orig/lib/ansible/modules/system/hostname.py 2018-06-15 05:20:41.000000000 +0800 ++++ ansible-2.5.5/lib/ansible/modules/system/hostname.py 2021-12-16 16:28:49.652405482 +0800 +@@ -762,6 +762,10 @@ + distribution = 'Neon' + strategy_class = DebianStrategy + ++class OpenEulerHostname(Hostname): ++ platform = 'Linux' ++ distribution = 'Openeuler' ++ strategy_class = SystemdStrategy + + def main(): + module = AnsibleModule( +diff --color -Nur ansible-2.5.5.orig/lib/ansible/module_utils/facts/system/distribution.py ansible-2.5.5/lib/ansible/module_utils/facts/system/distribution.py +--- ansible-2.5.5.orig/lib/ansible/module_utils/facts/system/distribution.py 2018-06-15 05:20:41.000000000 +0800 ++++ ansible-2.5.5/lib/ansible/module_utils/facts/system/distribution.py 2021-12-16 14:16:19.859943412 +0800 +@@ -408,7 +408,7 @@ + # keep keys in sync with Conditionals page of docs + OS_FAMILY_MAP = {'RedHat': ['RedHat', 'Fedora', 'CentOS', 'Scientific', 'SLC', + 'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', +- 'OEL', 'Amazon', 'Virtuozzo', 'XenServer'], ++ 'OEL', 'Amazon', 'Virtuozzo', 'XenServer', 'openEuler'], + 'Debian': ['Debian', 'Ubuntu', 'Raspbian', 'Neon', 'KDE neon', + 'Linux Mint'], + 'Suse': ['SuSE', 'SLES', 'SLED', 'openSUSE', 'openSUSE Tumbleweed', +diff --color -Nur ansible-2.5.5.orig/test/sanity/import/lib/ansible/module_utils/facts/system/distribution.py ansible-2.5.5/test/sanity/import/lib/ansible/module_utils/facts/system/distribution.py +--- ansible-2.5.5.orig/test/sanity/import/lib/ansible/module_utils/facts/system/distribution.py 2018-06-15 05:20:41.000000000 +0800 ++++ ansible-2.5.5/test/sanity/import/lib/ansible/module_utils/facts/system/distribution.py 2021-12-16 14:16:19.859943412 +0800 +@@ -408,7 +408,7 @@ + # keep keys in sync with Conditionals page of docs + OS_FAMILY_MAP = {'RedHat': ['RedHat', 'Fedora', 'CentOS', 'Scientific', 'SLC', + 'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', +- 'OEL', 'Amazon', 'Virtuozzo', 'XenServer'], ++ 'OEL', 'Amazon', 'Virtuozzo', 'XenServer', 'openEuler'], + 'Debian': ['Debian', 'Ubuntu', 'Raspbian', 'Neon', 'KDE neon', + 'Linux Mint'], + 'Suse': ['SuSE', 'SLES', 'SLED', 'openSUSE', 'openSUSE Tumbleweed', diff --git a/ansible.spec b/ansible.spec index 20344d3..9dcffc8 100644 --- a/ansible.spec +++ b/ansible.spec @@ -3,7 +3,7 @@ Name: ansible Summary: SSH-based configuration management, deployment, and task execution system Version: 2.5.5 -Release: 4 +Release: 5 License: Python-2.0 and MIT and GPL+ Url: http://ansible.com Source0: https://releases.ansible.com/ansible/%{name}-%{version}.tar.gz @@ -12,7 +12,6 @@ Patch100: ansible-newer-jinja.patch Patch101: CVE-2019-14904.patch Patch102: CVE-2020-10684.patch Patch103: CVE-2020-10729.patch -Patch104: CVE-2020-1735.patch Patch106: CVE-2020-1737.patch Patch108: CVE-2020-1739.patch Patch109: CVE-2020-1740.patch @@ -20,6 +19,9 @@ Patch110: CVE-2020-1753.patch Patch111: CVE-2021-20191.patch Patch112: CVE-2019-10156-1.patch Patch113: CVE-2019-10156-2.patch +Patch114: CVE-2020-1735.patch +Patch115: CVE-2019-3828.patch +Patch116: ansible-2.5.5-openEuler-hostname.patch BuildArch: noarch Provides: ansible-fireball = %{version}-%{release} Obsoletes: ansible-fireball < 1.2.4 @@ -77,7 +79,6 @@ This package installs extensive documentation for ansible %patch101 -p1 %patch102 -p1 %patch103 -p1 -%patch104 -p1 %patch106 -p1 %patch108 -p1 %patch109 -p1 @@ -85,7 +86,9 @@ This package installs extensive documentation for ansible %patch111 -p1 %patch112 -p1 %patch113 -p1 - +%patch114 -p1 +%patch115 -p1 +%patch116 -p1 %if 0%{?with_python3} rm -rf %{py3dir} cp -a . %{py3dir} @@ -146,6 +149,10 @@ cp -pr docs/docsite/rst . %endif %changelog +* Mon Feb 28 2022 wangkai - 2.5.5-5 +- Fix CVE-2019-3828 and hostname module support openEuler +- and add defination of is_subpath and remove module_response_deepcopy + * Thu Oct 28 2021 liwu - 2.5.5-4 - The upstream community rolls back the patch