fix CVE-2019-14904 CVE-2020-10684 CVE-2020-10729 CVE-2020-1735-to-CVE-2020-1740 CVE-2020-1753 CVE-2021-20191

(cherry picked from commit ebf023f03ad09762c8147ad8c963a51b60de62ff)
This commit is contained in:
starlet-dx 2021-09-17 18:09:57 +08:00 committed by openeuler-sync-bot
parent 2771f99ec4
commit c52b423a72
12 changed files with 1334 additions and 1 deletions

68
CVE-2019-14904.patch Normal file
View File

@ -0,0 +1,68 @@
From e5a2138219d497491d2a7e07ba558737548f0e69 Mon Sep 17 00:00:00 2001
From: Abhijeet Kasurde <akasurde@redhat.com>
Date: Tue, 10 Dec 2019 16:58:37 +0530
Subject: [PATCH] solaris_zone: Allow only valid characters in zone name
CVE-2019-14904 - solaris_zone module accepts zone name and performs actions related to that.
However, there is no user input validation done while performing actions.
A malicious user could provide a crafted zone name which allows executing commands
into the server manipulating the module behaviour.
Adding user input validation as per Solaris Zone documentation fixes this issue.
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
---
changelogs/fragments/solaris_zone_name_fix.yml | 5 +++++
lib/ansible/modules/system/solaris_zone.py | 10 ++++++++++
2 files changed, 15 insertions(+)
create mode 100644 changelogs/fragments/solaris_zone_name_fix.yml
diff --git a/changelogs/fragments/solaris_zone_name_fix.yml b/changelogs/fragments/solaris_zone_name_fix.yml
new file mode 100644
index 0000000..eea9785
--- /dev/null
+++ b/changelogs/fragments/solaris_zone_name_fix.yml
@@ -0,0 +1,5 @@
+bugfixes:
+- "**SECURITY** - CVE-2019-14904 - solaris_zone module accepts zone name and performs actions related to that.
+ However, there is no user input validation done while performing actions. A malicious user could provide a
+ crafted zone name which allows executing commands into the server manipulating the module behaviour. Adding
+ user input validation as per Solaris Zone documentation fixes this issue."
diff --git a/lib/ansible/modules/system/solaris_zone.py b/lib/ansible/modules/system/solaris_zone.py
index bbeb803..2cb76ec 100644
--- a/lib/ansible/modules/system/solaris_zone.py
+++ b/lib/ansible/modules/system/solaris_zone.py
@@ -41,6 +41,10 @@ options:
name:
description:
- Zone name.
+ - A zone name must be unique name.
+ - A zone name must begin with an alpha-numeric character.
+ - The name can contain alpha-numeric characters, underbars I(_), hyphens I(-), and periods I(.).
+ - The name cannot be longer than 64 characters.
required: true
path:
description:
@@ -137,6 +141,7 @@ EXAMPLES = '''
import os
import platform
+import re
import tempfile
import time
@@ -173,6 +178,11 @@ class Zone(object):
if int(self.os_minor) < 10:
self.module.fail_json(msg='This module requires Solaris 10 or later')
+ match = re.match('^[a-zA-Z0-9][-_.a-zA-Z0-9]{0,62}$', self.name)
+ if not match:
+ self.module.fail_json(msg="Provided zone name is not a valid zone name. "
+ "Please refer documentation for correct zone name specifications.")
+
def configure(self):
if not self.path:
self.module.fail_json(msg='Missing required argument: path')
--
2.27.0

111
CVE-2020-10684.patch Normal file
View File

@ -0,0 +1,111 @@
From e26374d30ced2585e6f10a2488a54ad23a115f8b Mon Sep 17 00:00:00 2001
From: Brian Coca <brian.coca+git@gmail.com>
Date: Wed, 18 Mar 2020 11:11:52 -0400
Subject: [PATCH] prevent ansible_facts injection
- also only replace when needed
- switched from replace to index
- added test to verify bogus_facts are not accepted
---
changelogs/fragments/af_clean.yml | 2 ++
lib/ansible/constants.py | 2 +-
lib/ansible/vars/clean.py | 7 +++----
.../targets/gathering_facts/library/bogus_facts | 12 ++++++++++++
test/integration/targets/gathering_facts/runme.sh | 3 +++
.../gathering_facts/test_prevent_injection.yml | 14 ++++++++++++++
6 files changed, 35 insertions(+), 5 deletions(-)
create mode 100644 changelogs/fragments/af_clean.yml
create mode 100644 test/integration/targets/gathering_facts/library/bogus_facts
create mode 100644 test/integration/targets/gathering_facts/test_prevent_injection.yml
diff --git a/changelogs/fragments/af_clean.yml b/changelogs/fragments/af_clean.yml
new file mode 100644
index 0000000..9d14ca5
--- /dev/null
+++ b/changelogs/fragments/af_clean.yml
@@ -0,0 +1,2 @@
+bugfixes:
+ - Ensure we don't allow ansible_facts subkey of ansible_facts to override top level, also fix 'deprefixing' to prevent key transforms.
diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py
index cdf2d5f..9a2073c 100644
--- a/lib/ansible/constants.py
+++ b/lib/ansible/constants.py
@@ -100,7 +100,7 @@ INTERNAL_RESULT_KEYS = ('add_host', 'add_group')
LOCALHOST = ('127.0.0.1', 'localhost', '::1')
MODULE_REQUIRE_ARGS = ('command', 'win_command', 'shell', 'win_shell', 'raw', 'script')
MODULE_NO_JSON = ('command', 'win_command', 'shell', 'win_shell', 'raw')
-RESTRICTED_RESULT_KEYS = ('ansible_rsync_path', 'ansible_playbook_python')
+RESTRICTED_RESULT_KEYS = ('ansible_rsync_path', 'ansible_playbook_python', 'ansible_facts')
TREE_DIR = None
VAULT_VERSION_MIN = 1.0
VAULT_VERSION_MAX = 1.0
diff --git a/lib/ansible/vars/clean.py b/lib/ansible/vars/clean.py
index c83709d..0a4fe07 100644
--- a/lib/ansible/vars/clean.py
+++ b/lib/ansible/vars/clean.py
@@ -106,10 +106,9 @@ def namespace_facts(facts):
''' return all facts inside 'ansible_facts' w/o an ansible_ prefix '''
deprefixed = {}
for k in facts:
- if k in ('ansible_local',):
- # 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])
else:
- deprefixed[k.replace('ansible_', '', 1)] = deepcopy(facts[k])
+ deprefixed[k] = module_response_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
new file mode 100644
index 0000000..a6aeede
--- /dev/null
+++ b/test/integration/targets/gathering_facts/library/bogus_facts
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+echo '{
+ "changed": false,
+ "ansible_facts": {
+ "ansible_facts": {
+ "discovered_interpreter_python": "(touch /tmp/pwned-$(date -Iseconds)-$(whoami) ) 2>/dev/null >/dev/null && /usr/bin/python",
+ "bogus_overwrite": "yes"
+ },
+ "dansible_iscovered_interpreter_python": "(touch /tmp/pwned-$(date -Iseconds)-$(whoami) ) 2>/dev/null >/dev/null && /usr/bin/python"
+ }
+}'
diff --git a/test/integration/targets/gathering_facts/runme.sh b/test/integration/targets/gathering_facts/runme.sh
index e4c7b38..4b80852 100755
--- a/test/integration/targets/gathering_facts/runme.sh
+++ b/test/integration/targets/gathering_facts/runme.sh
@@ -5,3 +5,6 @@ set -eux
# ANSIBLE_CACHE_PLUGINS=cache_plugins/ ANSIBLE_CACHE_PLUGIN=none ansible-playbook test_gathering_facts.yml -i ../../inventory -v "$@"
ansible-playbook test_gathering_facts.yml -i ../../inventory -v "$@"
#ANSIBLE_CACHE_PLUGIN=base ansible-playbook test_gathering_facts.yml -i ../../inventory -v "$@"
+
+# ensure clean_facts is working properly
+ansible-playbook test_prevent_injection.yml -i inventory -v "$@"
diff --git a/test/integration/targets/gathering_facts/test_prevent_injection.yml b/test/integration/targets/gathering_facts/test_prevent_injection.yml
new file mode 100644
index 0000000..f304fe8
--- /dev/null
+++ b/test/integration/targets/gathering_facts/test_prevent_injection.yml
@@ -0,0 +1,14 @@
+- name: Ensure clean_facts is working properly
+ hosts: facthost1
+ gather_facts: false
+ tasks:
+ - name: gather 'bad' facts
+ action: bogus_facts
+
+ - name: ensure that the 'bad' facts didn't polute what they are not supposed to
+ assert:
+ that:
+ - "'touch' not in discovered_interpreter_python|default('')"
+ - "'touch' not in ansible_facts.get('discovered_interpreter_python', '')"
+ - "'touch' not in ansible_facts.get('ansible_facts', {}).get('discovered_interpreter_python', '')"
+ - bogus_overwrite is undefined
--
2.27.0

22
CVE-2020-10729.patch Normal file
View File

@ -0,0 +1,22 @@
From fdea9d251516ad09d2f5eef9a11a6e622355db7c Mon Sep 17 00:00:00 2001
From: Felix Fontein <felix@fontein.de>
Date: Fri, 14 Feb 2020 18:21:14 +0100
Subject: [PATCH] Make sure only one variable results are cached.
---
lib/ansible/template/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
index 7cea02bc77187..b4e8118a4dd12 100644
--- a/lib/ansible/template/__init__.py
+++ b/lib/ansible/template/__init__.py
@@ -498,7 +498,7 @@ def template(self, variable, convert_bare=False, preserve_trailing_newlines=True
# we only cache in the case where we have a single variable
# name, to make sure we're not putting things which may otherwise
# be dynamic in the cache (filters, lookups, etc.)
- if cache:
+ if cache and only_one:
self._cached_result[sha1_hash] = result
return result

215
CVE-2020-1735.patch Normal file
View File

@ -0,0 +1,215 @@
From 5292482553dc409081f7f4368398358cbf9f8672 Mon Sep 17 00:00:00 2001
From: Brian Coca <bcoca@users.noreply.github.com>
Date: Wed, 8 Apr 2020 14:28:51 -0400
Subject: [PATCH] fixed fetch traversal from slurp (#68720)
* 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 +++++++++++++++++
.../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(-)
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
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
+++ 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
--- a/lib/ansible/plugins/action/fetch.py
+++ b/lib/ansible/plugins/action/fetch.py
@@ -106,12 +106,6 @@ class ActionModule(ActionBase):
remote_data = base64.b64decode(slurpres['content'])
if remote_data is not None:
remote_checksum = checksum_s(remote_data)
- # the source path may have been expanded on the
- # target system, so we compare it here and use the
- # expanded version if it's different
- remote_source = slurpres.get('source')
- if remote_source and remote_source != source:
- source = remote_source
# calculate the destination name
if os.path.sep not in self._connection._shell.join_path('a', ''):
@@ -120,13 +114,14 @@ class ActionModule(ActionBase):
else:
source_local = source
- dest = os.path.expanduser(dest)
+ # 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))
+
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"
- result['file'] = dest
- result['failed'] = True
- return result
+ raise AnsibleActionFail("dest is an existing directory, use a trailing slash if you want to fetch src into that directory")
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):
target_name = self._play_context.remote_addr
dest = "%s/%s/%s" % (self._loader.path_dwim(dest), target_name, source_local)
- dest = dest.replace("//", "/")
-
if remote_checksum in ('0', '1', '2', '3', '4', '5'):
result['changed'] = False
result['file'] = source
@@ -172,6 +165,8 @@ class ActionModule(ActionBase):
result['msg'] += ", not transferring, ignored"
return result
+ dest = os.path.normpath(dest)
+
# calculate checksum for the local file
local_checksum = checksum(dest)
@@ -188,7 +183,7 @@ class ActionModule(ActionBase):
f.write(remote_data)
f.close()
except (IOError, OSError) as e:
- raise AnsibleError("Failed to fetch the file: %s" % e)
+ raise AnsibleActionFail("Failed to fetch the file: %s" % e)
new_checksum = secure_hash(dest)
# For backwards compatibility. We'll return None on FIPS enabled systems
try:
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
--- /dev/null
+++ b/test/integration/targets/fetch/injection/avoid_slurp_return.yml
@@ -0,0 +1,26 @@
+- name: ensure that 'fake slurp' does not poison fetch source
+ hosts: localhost
+ gather_facts: False
+ tasks:
+ - name: fetch with relative source path
+ fetch: src=../injection/here.txt dest={{output_dir}}
+ become: true
+ register: islurp
+
+ - name: fetch with normal source path
+ fetch: src=here.txt dest={{output_dir}}
+ become: true
+ register: islurp2
+
+ - name: ensure all is good in hollywood
+ assert:
+ that:
+ - "'..' not in islurp['dest']"
+ - "'..' not in islurp2['dest']"
+ - "'foo' not in islurp['dest']"
+ - "'foo' not in islurp2['dest']"
+
+ - name: try to trip dest anyways
+ fetch: src=../injection/here.txt dest={{output_dir}}
+ become: true
+ register: islurp2
diff --git a/test/integration/targets/fetch/injection/here.txt b/test/integration/targets/fetch/injection/here.txt
new file mode 100644
index 0000000..493021b
--- /dev/null
+++ b/test/integration/targets/fetch/injection/here.txt
@@ -0,0 +1 @@
+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
--- /dev/null
+++ b/test/integration/targets/fetch/injection/library/slurp.py
@@ -0,0 +1,29 @@
+#!/usr/bin/python
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+ module: fakeslurp
+ short_desciptoin: fake slurp module
+ description:
+ - this is a fake slurp module
+ options:
+ _notreal:
+ description: really not a real slurp
+ author:
+ - me
+"""
+
+import json
+import random
+
+bad_responses = ['../foo', '../../foo', '../../../foo', '/../../../foo', '/../foo', '//..//foo', '..//..//foo']
+
+
+def main():
+ print(json.dumps(dict(changed=False, content='', encoding='base64', source=random.choice(bad_responses))))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/integration/targets/fetch/run_fetch_tests.yml b/test/integration/targets/fetch/run_fetch_tests.yml
new file mode 100644
index 0000000..f2ff1df
--- /dev/null
+++ b/test/integration/targets/fetch/run_fetch_tests.yml
@@ -0,0 +1,5 @@
+- name: call fetch_tests role
+ hosts: testhost
+ gather_facts: false
+ 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
--- /dev/null
+++ b/test/integration/targets/fetch/runme.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -eux
+
+# setup required roles
+ln -s ../../setup_remote_tmp_dir roles/setup_remote_tmp_dir
+
+# run old type role tests
+ansible-playbook -i ../../inventory run_fetch_tests.yml -e "output_dir=${OUTPUT_DIR}" -v "$@"
+
+# run tests to avoid path injection from slurp when fetch uses become
+ansible-playbook -i ../../inventory injection/avoid_slurp_return.yml -e "output_dir=${OUTPUT_DIR}" -v "$@"
--
2.27.0

74
CVE-2020-1736.patch Normal file
View File

@ -0,0 +1,74 @@
From a2ef19e48a53cc83b3a6f433013d8ff4e8f5d618 Mon Sep 17 00:00:00 2001
From: Brian Coca <brian.coca+git@gmail.com>
Date: Thu, 2 Apr 2020 11:07:51 -0400
Subject: [PATCH] stricter permissions on atomic_move when creating new file
---
test/units/module_utils/basic/test_atomic_move.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/test/units/module_utils/basic/test_atomic_move.py b/test/units/module_utils/basic/test_atomic_move.py
index d1dc4d7..a44ebc5 100644
--- a/test/units/module_utils/basic/test_atomic_move.py
+++ b/test/units/module_utils/basic/test_atomic_move.py
@@ -59,7 +59,7 @@ def atomic_mocks(mocker):
@pytest.fixture
def fake_stat(mocker):
stat1 = mocker.MagicMock()
- stat1.st_mode = 0o0644
+ stat1.st_mode = 0o0640
stat1.st_uid = 0
stat1.st_gid = 0
yield stat1
@@ -75,7 +75,8 @@ def test_new_file(atomic_am, atomic_mocks, mocker, selinux):
atomic_am.atomic_move('/path/to/src', '/path/to/dest')
atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
- assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', basic.DEFAULT_PERM & ~18)]
+ # 416 is what we expect with default perms set to 0640
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', 416)]
if selinux:
assert atomic_am.selinux_default_context.call_args_list == [mocker.call('/path/to/dest')]
@@ -96,7 +97,7 @@ def test_existing_file(atomic_am, atomic_mocks, fake_stat, mocker, selinux):
atomic_am.atomic_move('/path/to/src', '/path/to/dest')
atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
- assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', 416)]
if selinux:
assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
@@ -119,10 +120,10 @@ def test_no_tty_fallback(atomic_am, atomic_mocks, fake_stat, mocker):
atomic_am.atomic_move('/path/to/src', '/path/to/dest')
atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
- assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
assert atomic_am.selinux_context.call_args_list == [mocker.call('/path/to/dest')]
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
@@ -150,6 +151,8 @@ def test_existing_file_stat_perms_failure(atomic_am, atomic_mocks, mocker):
# FIXME: Should atomic_move() set a default permission value when it cannot retrieve the
# existing file's permissions? (Right now it's up to the calling code.
# assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
+ # atomic_move() will set a default permission value whenit cannot retrieve the
+ # existing file's permissions.
assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
assert atomic_am.selinux_context.call_args_list == [mocker.call('/path/to/dest')]
@@ -206,7 +209,7 @@ def test_rename_perms_fail_temp_succeeds(atomic_am, atomic_mocks, fake_stat, moc
atomic_am.atomic_move('/path/to/src', '/path/to/dest')
assert atomic_mocks['rename'].call_args_list == [mocker.call(b'/path/to/src', b'/path/to/dest'),
mocker.call(b'/path/to/tempfile', b'/path/to/dest')]
- assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', basic.DEFAULT_PERM & ~18)]
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', 416)]
if selinux:
assert atomic_am.selinux_default_context.call_args_list == [mocker.call('/path/to/dest')]
--
2.23.0

28
CVE-2020-1737.patch Normal file
View File

@ -0,0 +1,28 @@
From 2d9910d0fe3a411f5bec96ecc1d082c9c6e34153 Mon Sep 17 00:00:00 2001
From: Sam Doran <sdoran@redhat.com>
Date: Tue, 25 Feb 2020 15:13:36 -0500
Subject: [PATCH] win_unzip - ensure extraction path ends withpath sep
---
lib/ansible/modules/windows/win_unzip.ps1 | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lib/ansible/modules/windows/win_unzip.ps1 b/lib/ansible/modules/windows/win_unzip.ps1
index abda148..7615784 100644
--- a/lib/ansible/modules/windows/win_unzip.ps1
+++ b/lib/ansible/modules/windows/win_unzip.ps1
@@ -54,6 +54,11 @@ Function Extract-Zip($src, $dest) {
$entry_target_path = [System.IO.Path]::Combine($dest, $archive_name)
$entry_dir = [System.IO.Path]::GetDirectoryName($entry_target_path)
+ # Ensure directory ends with path separator to prevent path traversal
+ if (-not $entry_dir.EndsWith([System.IO.Path]::DirectorySeparatorChar.ToString())) {
+ $entry_dir += [System.IO.Path]::DirectorySeparatorChar.ToString()
+ }
+
if (-not (Test-Path -Path $entry_dir)) {
New-Item -Path $entry_dir -ItemType Directory -WhatIf:$check_mode | Out-Null
$result.changed = $true
--
2.23.0

69
CVE-2020-1738.patch Normal file
View File

@ -0,0 +1,69 @@
From b1fd71de03ae3843ac556d9b726b5f3b2441c3ed Mon Sep 17 00:00:00 2001
From: Abhijeet Kasurde <akasurde@redhat.com>
Date: Thu, 27 Feb 2020 11:42:12 +0530
Subject: [PATCH] Add whitelisting for package and service module
**security issue** (CVE-2020-1738)
When 'use' parameter is not used in package and service module,
ansible relies on ansible facts such as 'pkg_mgr' and 'service_mgr'.
This would allow arbitrary code execution on the managed node.
Fix is added by adding a whitelist of allowed package manager modules and
service manager modules to avoid arbitrary code execution on the managed node.
Fixes: #67796
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
---
changelogs/fragments/67796-package-service-fact_fix.yml | 4 ++++
lib/ansible/plugins/action/package.py | 8 ++++++++
lib/ansible/plugins/action/service.py | 5 +++++
3 files changed, 17 insertions(+)
create mode 100644 changelogs/fragments/67796-package-service-fact_fix.yml
diff --git a/changelogs/fragments/67796-package-service-fact_fix.yml b/changelogs/fragments/67796-package-service-fact_fix.yml
new file mode 100644
index 0000000000000..ce1ee71da08e0
--- /dev/null
+++ b/changelogs/fragments/67796-package-service-fact_fix.yml
@@ -0,0 +1,4 @@
+bugfixes:
+ - >
+ **security issue** Add a whitelist of modules for package and service module
+ when 'use' is not used and engine relies on pkg_mgr and service_mgr facts (CVE-2020-1738).
diff --git a/lib/ansible/plugins/action/package.py b/lib/ansible/plugins/action/package.py
index 932acccb04b66..8884086d8d6c5 100644
--- a/lib/ansible/plugins/action/package.py
+++ b/lib/ansible/plugins/action/package.py
@@ -56,6 +56,14 @@ def run(self, tmp=None, task_vars=None):
module = facts.get('ansible_facts', {}).get('ansible_pkg_mgr', 'auto')
if module != 'auto':
+ if module not in ['apk', 'apt_rpm', 'apt', 'dnf', 'homebrew_cask',
+ 'homebrew_tap', 'homebrew', 'installp', 'macports', 'mas',
+ 'openbsd_pkg', 'opkg', 'pacman', 'pkg5', 'pkgin',
+ 'pkgng', 'pkgutil', 'portage', 'portinstall', 'slackpkg',
+ 'snap', 'sorcery', 'svr4pkg', 'swdepot', 'swupd',
+ 'urpmi', 'xbps', 'yum', 'zypper']:
+ raise AnsibleActionFail('Could not find a module for package manager %s.'
+ 'Try setting the "use" option.' % module)
if module not in self._shared_loader_obj.module_loader:
raise AnsibleActionFail('Could not find a module for %s.' % module)
diff --git a/lib/ansible/plugins/action/service.py b/lib/ansible/plugins/action/service.py
index 3ebd0ae17dc90..e11ab1e287164 100644
--- a/lib/ansible/plugins/action/service.py
+++ b/lib/ansible/plugins/action/service.py
@@ -61,6 +61,11 @@ def run(self, tmp=None, task_vars=None):
module = 'service'
if module != 'auto':
+ # Check if auto detected module is valid module name or not
+ if module not in ['nosh', 'openwrt_init', 'runit',
+ 'svc', 'systemd', 'sysvinit', 'service']:
+ raise AnsibleActionFail('Could not find module for "%s" service manager. '
+ 'Try setting the "use" option.' % module)
# run the 'service' module
new_module_args = self._task.args.copy()
if 'use' in new_module_args:

384
CVE-2020-1739.patch Normal file
View File

@ -0,0 +1,384 @@
From 641ff410128a959884b035a2c720c3ba61bf2064 Mon Sep 17 00:00:00 2001
From: Sloane Hertel <shertel@redhat.com>
Date: Mon, 13 Apr 2020 10:21:10 -0400
Subject: [PATCH] subversion module - provide password securely when possible
or warn (#67829)
* subversion module - provide password securely with svn command line option --password-from-stdin when possible, and provide a warning otherwise.
* Update lib/ansible/modules/source_control/subversion.py.
* Add a test.
Co-authored-by: Sam Doran <sdoran@redhat.com>
(cherry picked from commit d91658ec0c8434c82c3ef98bfe9eb4e1027a43a3)
---
changelogs/fragments/subversion_password.yaml | 9 ++
.../modules/source_control/subversion.py | 21 ++-
test/integration/targets/subversion/aliases | 1 +
.../targets/subversion/meta/main.yml | 2 -
.../roles/subversion/tasks/cleanup.yml | 8 ++
.../roles/subversion/tasks/main.yml | 20 +++
.../roles/subversion/tasks/setup_selinux.yml | 11 ++
.../roles/subversion/tasks/warnings.yml | 7 +
test/integration/targets/subversion/runme.sh | 32 +++++
test/integration/targets/subversion/runme.yml | 15 +++
.../targets/subversion/tasks/main.yml | 123 ------------------
11 files changed, 121 insertions(+), 128 deletions(-)
create mode 100644 changelogs/fragments/subversion_password.yaml
delete mode 100644 test/integration/targets/subversion/meta/main.yml
create mode 100644 test/integration/targets/subversion/roles/subversion/tasks/cleanup.yml
create mode 100644 test/integration/targets/subversion/roles/subversion/tasks/main.yml
create mode 100644 test/integration/targets/subversion/roles/subversion/tasks/setup_selinux.yml
create mode 100644 test/integration/targets/subversion/roles/subversion/tasks/warnings.yml
create mode 100755 test/integration/targets/subversion/runme.sh
create mode 100644 test/integration/targets/subversion/runme.yml
delete mode 100644 test/integration/targets/subversion/tasks/main.yml
diff --git a/changelogs/fragments/subversion_password.yaml b/changelogs/fragments/subversion_password.yaml
new file mode 100644
index 0000000..42e09fb
--- /dev/null
+++ b/changelogs/fragments/subversion_password.yaml
@@ -0,0 +1,9 @@
+bugfixes:
+- >
+ **security issue** - The ``subversion`` module provided the password
+ via the svn command line option ``--password`` and can be retrieved
+ from the host's /proc/<pid>/cmdline file. Update the module to use
+ the secure ``--password-from-stdin`` option instead, and add a warning
+ in the module and in the documentation if svn version is too old to
+ support it.
+ (CVE-2020-1739)
diff --git a/lib/ansible/modules/source_control/subversion.py b/lib/ansible/modules/source_control/subversion.py
index 3b2547d..e9112cb 100644
--- a/lib/ansible/modules/source_control/subversion.py
+++ b/lib/ansible/modules/source_control/subversion.py
@@ -49,7 +49,9 @@ options:
- C(--username) parameter passed to svn.
password:
description:
- - C(--password) parameter passed to svn.
+ - C(--password) parameter passed to svn when svn is less than version 1.10.0. This is not secure and
+ the password will be leaked to argv.
+ - C(--password-from-stdin) parameter when svn is greater or equal to version 1.10.0.
executable:
description:
- Path to svn executable to use. If not supplied,
@@ -103,6 +105,8 @@ EXAMPLES = '''
import os
import re
+from distutils.version import LooseVersion
+
from ansible.module_utils.basic import AnsibleModule
@@ -116,6 +120,10 @@ class Subversion(object):
self.password = password
self.svn_path = svn_path
+ def has_option_password_from_stdin(self):
+ rc, version, err = self.module.run_command([self.svn_path, '--version', '--quiet'], check_rc=True)
+ return LooseVersion(version) >= LooseVersion('1.10.0')
+
def _exec(self, args, check_rc=True):
'''Execute a subversion command, and return output. If check_rc is False, returns the return code instead of the output.'''
bits = [
@@ -124,12 +132,19 @@ class Subversion(object):
'--trust-server-cert',
'--no-auth-cache',
]
+ stdin_data = None
if self.username:
bits.extend(["--username", self.username])
if self.password:
- bits.extend(["--password", self.password])
+ if self.has_option_password_from_stdin():
+ bits.append("--password-from-stdin")
+ stdin_data = self.password
+ else:
+ self.module.warn("The authentication provided will be used on the svn command line and is not secure. "
+ "To securely pass credentials, upgrade svn to version 1.10.0 or greater.")
+ bits.extend(["--password", self.password])
bits.extend(args)
- rc, out, err = self.module.run_command(bits, check_rc)
+ rc, out, err = self.module.run_command(bits, check_rc, data=stdin_data)
if check_rc:
return out.splitlines()
else:
diff --git a/test/integration/targets/subversion/aliases b/test/integration/targets/subversion/aliases
index c364b48..8be436d 100644
--- a/test/integration/targets/subversion/aliases
+++ b/test/integration/targets/subversion/aliases
@@ -1,2 +1,3 @@
+setup/always/setup_passlib
posix/ci/group2
skip/osx
diff --git a/test/integration/targets/subversion/meta/main.yml b/test/integration/targets/subversion/meta/main.yml
deleted file mode 100644
index 07faa21..0000000
--- a/test/integration/targets/subversion/meta/main.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-dependencies:
- - prepare_tests
diff --git a/test/integration/targets/subversion/roles/subversion/tasks/cleanup.yml b/test/integration/targets/subversion/roles/subversion/tasks/cleanup.yml
new file mode 100644
index 0000000..9be43b4
--- /dev/null
+++ b/test/integration/targets/subversion/roles/subversion/tasks/cleanup.yml
@@ -0,0 +1,8 @@
+---
+- name: stop apache after tests
+ shell: "kill -9 $(cat '{{ subversion_server_dir }}/apache.pid')"
+
+- name: remove tmp subversion server dir
+ file:
+ path: '{{ subversion_server_dir }}'
+ state: absent
diff --git a/test/integration/targets/subversion/roles/subversion/tasks/main.yml b/test/integration/targets/subversion/roles/subversion/tasks/main.yml
new file mode 100644
index 0000000..0d6acb8
--- /dev/null
+++ b/test/integration/targets/subversion/roles/subversion/tasks/main.yml
@@ -0,0 +1,20 @@
+---
+- name: setup subversion server
+ import_tasks: setup.yml
+ tags: setup
+
+- name: verify that subversion is installed so this test can continue
+ shell: which svn
+ tags: always
+
+- name: run tests
+ import_tasks: tests.yml
+ tags: tests
+
+- name: run warning
+ import_tasks: warnings.yml
+ tags: warnings
+
+- name: clean up
+ import_tasks: cleanup.yml
+ tags: cleanup
diff --git a/test/integration/targets/subversion/roles/subversion/tasks/setup_selinux.yml b/test/integration/targets/subversion/roles/subversion/tasks/setup_selinux.yml
new file mode 100644
index 0000000..a9ffa71
--- /dev/null
+++ b/test/integration/targets/subversion/roles/subversion/tasks/setup_selinux.yml
@@ -0,0 +1,11 @@
+- name: set SELinux security context for SVN folder
+ sefcontext:
+ target: '{{ subversion_server_dir }}(/.*)?'
+ setype: '{{ item }}'
+ state: present
+ with_items:
+ - httpd_sys_content_t
+ - httpd_sys_rw_content_t
+
+- name: apply new SELinux context to filesystem
+ command: restorecon -irv {{ subversion_server_dir | quote }}
diff --git a/test/integration/targets/subversion/roles/subversion/tasks/warnings.yml b/test/integration/targets/subversion/roles/subversion/tasks/warnings.yml
new file mode 100644
index 0000000..50ebd44
--- /dev/null
+++ b/test/integration/targets/subversion/roles/subversion/tasks/warnings.yml
@@ -0,0 +1,7 @@
+---
+- name: checkout using a password to test for a warning when using svn lt 1.10.0
+ subversion:
+ repo: '{{ subversion_repo_auth_url }}'
+ dest: '{{ subversion_test_dir }}/svn'
+ username: '{{ subversion_username }}'
+ password: '{{ subversion_password }}'
diff --git a/test/integration/targets/subversion/runme.sh b/test/integration/targets/subversion/runme.sh
new file mode 100755
index 0000000..f505e58
--- /dev/null
+++ b/test/integration/targets/subversion/runme.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+set -eu
+
+cleanup() {
+ echo "Cleanup"
+ ansible-playbook runme.yml -e "output_dir=${OUTPUT_DIR}" "$@" --tags cleanup
+ echo "Done"
+}
+
+trap cleanup INT TERM EXIT
+
+export ANSIBLE_ROLES_PATH=roles/
+
+# Ensure subversion is set up
+ansible-playbook runme.yml "$@" -v --tags setup
+
+# Test functionality
+ansible-playbook runme.yml "$@" -v --tags tests
+
+# Test a warning is displayed for versions < 1.10.0 when a password is provided
+ansible-playbook runme.yml "$@" --tags warnings 2>&1 | tee out.txt
+
+version="$(svn --version -q)"
+secure=$(python -c "from distutils.version import LooseVersion; print(LooseVersion('$version') >= LooseVersion('1.10.0'))")
+
+if [[ "${secure}" = "False" ]] && [[ "$(grep -c 'To securely pass credentials, upgrade svn to version 1.10.0' out.txt)" -eq 1 ]]; then
+ echo "Found the expected warning"
+elif [[ "${secure}" = "False" ]]; then
+ echo "Expected a warning"
+ exit 1
+fi
diff --git a/test/integration/targets/subversion/runme.yml b/test/integration/targets/subversion/runme.yml
new file mode 100644
index 0000000..c67d7b8
--- /dev/null
+++ b/test/integration/targets/subversion/runme.yml
@@ -0,0 +1,15 @@
+---
+- hosts: localhost
+ tasks:
+ - name: load OS specific vars
+ include_vars: '{{ item }}'
+ with_first_found:
+ - files:
+ - '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml'
+ - '{{ ansible_os_family }}.yml'
+ paths: '../vars'
+ tags: always
+
+ - include_role:
+ name: subversion
+ tags: always
diff --git a/test/integration/targets/subversion/tasks/main.yml b/test/integration/targets/subversion/tasks/main.yml
deleted file mode 100644
index 5631adb..0000000
--- a/test/integration/targets/subversion/tasks/main.yml
+++ /dev/null
@@ -1,123 +0,0 @@
-# test code for the svn module
-# (c) 2014, Michael DeHaan <michael.dehaan@gmail.com>
-
-# This file is part of Ansible
-#
-# Ansible is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Ansible is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-
-- name: set where to extract the repo
- set_fact: checkout_dir={{ output_dir }}/svn
-
-- name: set what repo to use
- set_fact: repo=https://github.com/jimi-c/test_role
-
-- name: clean out the output_dir
- shell: rm -rf {{ output_dir }}/*
-
-- name: install subversion
- package:
- name: subversion
- when: ansible_distribution != "MacOSX"
-
-- name: verify that subversion is installed so this test can continue
- shell: which svn
-
-# checks out every branch so using a small repo
-
-- name: initial checkout
- subversion: repo={{ repo }} dest={{ checkout_dir }}
- register: subverted
-
-- debug: var=subverted
-
-- shell: ls {{ checkout_dir }}
-
-# FIXME: the before/after logic here should be fixed to make them hashes, see GitHub 6078
-# looks like this: {
-# "after": [
-# "Revision: 9",
-# "URL: https://github.com/jimi-c/test_role"
-# ],
-# "before": null,
-# "changed": true,
-# "item": ""
-# }
-
-- name: verify information about the initial clone
- assert:
- that:
- - "'after' in subverted"
- - "subverted.after.1 == 'URL: https://github.com/jimi-c/test_role'"
- - "not subverted.before"
- - "subverted.changed"
-
-- name: repeated checkout
- subversion: repo={{ repo }} dest={{ checkout_dir }}
- register: subverted2
-
-- name: verify on a reclone things are marked unchanged
- assert:
- that:
- - "not subverted2.changed"
-
-- name: check for tags
- stat: path={{ checkout_dir }}/tags
- register: tags
-
-- name: check for trunk
- stat: path={{ checkout_dir }}/trunk
- register: trunk
-
-- name: check for branches
- stat: path={{ checkout_dir }}/branches
- register: branches
-
-- name: assert presence of tags/trunk/branches
- assert:
- that:
- - "tags.stat.isdir"
- - "trunk.stat.isdir"
- - "branches.stat.isdir"
-
-- name: checkout with quotes in username
- subversion: repo={{ repo }} dest={{ checkout_dir }} username="quoteme'''"
- register: subverted3
-
-- debug: var=subverted3
-
-- name: checkout with export
- subversion: repo={{ repo }} dest={{ output_dir }}/svn-export export=True
- register: subverted4
-
-- name: check for tags
- stat: path={{ output_dir }}/svn-export/tags
- register: export_tags
-
-- name: check for trunk
- stat: path={{ output_dir }}/svn-export/trunk
- register: export_trunk
-
-- name: check for branches
- stat: path={{ output_dir }}/svn-export/branches
- register: export_branches
-
-- name: assert presence of tags/trunk/branches in export
- assert:
- that:
- - "export_tags.stat.isdir"
- - "export_trunk.stat.isdir"
- - "export_branches.stat.isdir"
- - "subverted4.changed"
-
-# TBA: test for additional options or URL variants welcome
--
2.27.0

229
CVE-2020-1740.patch Normal file
View File

@ -0,0 +1,229 @@
From 28f9fbdb5e281976e33f443193047068afb97a9b Mon Sep 17 00:00:00 2001
From: Brian Coca <bcoca@users.noreply.github.com>
Date: Fri, 3 Apr 2020 10:19:01 -0400
Subject: [PATCH] safely use vault to edit secrets (#68644)
* when possible, use filedescriptors from mkstemp to avoid race
* when using path strings, ensure we are always creating the file
CVE-2020-1740
Fixes #67798
Co-authored-by: samdoran
---
changelogs/fragments/vault_tmp_race_fix.yml | 2 +
lib/ansible/parsing/vault/__init__.py | 119 +++++++++++++-------
2 files changed, 82 insertions(+), 39 deletions(-)
create mode 100644 changelogs/fragments/vault_tmp_race_fix.yml
diff --git a/changelogs/fragments/vault_tmp_race_fix.yml b/changelogs/fragments/vault_tmp_race_fix.yml
new file mode 100644
index 0000000..5807e17
--- /dev/null
+++ b/changelogs/fragments/vault_tmp_race_fix.yml
@@ -0,0 +1,2 @@
+bugfixes:
+ - "**security_issue** - create temporary vault file with strict permissions when editing and prevent race condition (CVE-2020-1740)"
diff --git a/lib/ansible/parsing/vault/__init__.py b/lib/ansible/parsing/vault/__init__.py
index 7a68166..d52cf55 100644
--- a/lib/ansible/parsing/vault/__init__.py
+++ b/lib/ansible/parsing/vault/__init__.py
@@ -19,6 +19,8 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+import errno
+import fcntl
import os
import random
import shlex
@@ -27,6 +29,7 @@ import subprocess
import sys
import tempfile
import warnings
+
from binascii import hexlify
from binascii import unhexlify
from binascii import Error as BinasciiError
@@ -847,41 +850,47 @@ class VaultEditor:
os.remove(tmp_path)
- def _edit_file_helper(self, filename, secret,
- existing_data=None, force_save=False, vault_id=None):
+ def _edit_file_helper(self, filename, secret, existing_data=None, force_save=False, vault_id=None):
# Create a tempfile
root, ext = os.path.splitext(os.path.realpath(filename))
fd, tmp_path = tempfile.mkstemp(suffix=ext)
- os.close(fd)
try:
if existing_data:
- self.write_data(existing_data, tmp_path, shred=False)
+ self.write_data(existing_data, fd, shred=False)
+ except Exception:
+ # if an error happens, destroy the decrypted file
+ self._shred_file(tmp_path)
+ raise
+ finally:
+ os.close(fd)
+ try:
# drop the user into an editor on the tmp file
subprocess.call(self._editor_shell_command(tmp_path))
except:
- # whatever happens, destroy the decrypted file
+ # if an error happens, destroy the decrypted file
self._shred_file(tmp_path)
raise
b_tmpdata = self.read_data(tmp_path)
# Do nothing if the content has not changed
- if existing_data == b_tmpdata and not force_save:
- self._shred_file(tmp_path)
- return
+ if force_save or existing_data != b_tmpdata:
- # encrypt new data and write out to tmp
- # An existing vaultfile will always be UTF-8,
- # so decode to unicode here
- b_ciphertext = self.vault.encrypt(b_tmpdata, secret, vault_id=vault_id)
- self.write_data(b_ciphertext, tmp_path)
+ # encrypt new data and write out to tmp
+ # An existing vaultfile will always be UTF-8,
+ # so decode to unicode here
+ b_ciphertext = self.vault.encrypt(b_tmpdata, secret, vault_id=vault_id)
+ self.write_data(b_ciphertext, tmp_path)
- # shuffle tmp file into place
- self.shuffle_files(tmp_path, filename)
- display.vvvvv('Saved edited file "%s" encrypted using %s and vault id "%s"' % (filename, secret, vault_id))
+ # shuffle tmp file into place
+ self.shuffle_files(tmp_path, filename)
+ display.vvvvv(u'Saved edited file "%s" encrypted using %s and vault id "%s"' % (to_text(filename), to_text(secret), to_text(vault_id)))
+
+ # always shred temp, jic
+ self._shred_file(tmp_path)
def _real_path(self, filename):
# '-' is special to VaultEditor, dont expand it.
@@ -952,21 +961,17 @@ class VaultEditor:
# Figure out the vault id from the file, to select the right secret to re-encrypt it
# (duplicates parts of decrypt, but alas...)
- dummy, dummy, cipher_name, vault_id = parse_vaulttext_envelope(b_vaulttext,
- filename=filename)
+ dummy, dummy, cipher_name, vault_id = parse_vaulttext_envelope(b_vaulttext, filename=filename)
# vault id here may not be the vault id actually used for decrypting
# as when the edited file has no vault-id but is decrypted by non-default id in secrets
# (vault_id=default, while a different vault-id decrypted)
+ # we want to get rid of files encrypted with the AES cipher
+ force_save = (cipher_name not in CIPHER_WRITE_WHITELIST)
+
# Keep the same vault-id (and version) as in the header
- if cipher_name not in CIPHER_WRITE_WHITELIST:
- # we want to get rid of files encrypted with the AES cipher
- self._edit_file_helper(filename, vault_secret_used, existing_data=plaintext,
- force_save=True, vault_id=vault_id)
- else:
- self._edit_file_helper(filename, vault_secret_used, existing_data=plaintext,
- force_save=False, vault_id=vault_id)
+ self._edit_file_helper(filename, vault_secret_used, existing_data=plaintext, force_save=force_save, vault_id=vault_id)
def plaintext(self, filename):
@@ -1033,8 +1038,8 @@ class VaultEditor:
return data
- # TODO: add docstrings for arg types since this code is picky about that
- def write_data(self, data, filename, shred=True):
+ def write_data(self, data, thefile, shred=True, mode=0o600):
+ # TODO: add docstrings for arg types since this code is picky about that
"""Write the data bytes to given path
This is used to write a byte string to a file or stdout. It is used for
@@ -1051,28 +1056,64 @@ class VaultEditor:
should be a byte string and not a text type.
:arg data: the byte string (bytes) data
- :arg filename: filename to save 'data' to.
+ :arg thefile: file descriptor or filename to save 'data' to.
:arg shred: if shred==True, make sure that the original data is first shredded so that is cannot be recovered.
:returns: None
"""
# FIXME: do we need this now? data_bytes should always be a utf-8 byte string
b_file_data = to_bytes(data, errors='strict')
- # get a ref to either sys.stdout.buffer for py3 or plain old sys.stdout for py2
- # We need sys.stdout.buffer on py3 so we can write bytes to it since the plaintext
- # of the vaulted object could be anything/binary/etc
- output = getattr(sys.stdout, 'buffer', sys.stdout)
-
- if filename == '-':
+ # check if we have a file descriptor instead of a path
+ is_fd = False
+ try:
+ is_fd = (isinstance(thefile, int) and fcntl.fcntl(thefile, fcntl.F_GETFD) != -1)
+ except Exception:
+ pass
+
+ if is_fd:
+ # if passed descriptor, use that to ensure secure access, otherwise it is a string.
+ # assumes the fd is securely opened by caller (mkstemp)
+ os.ftruncate(thefile, 0)
+ os.write(thefile, b_file_data)
+ elif thefile == '-':
+ # get a ref to either sys.stdout.buffer for py3 or plain old sys.stdout for py2
+ # We need sys.stdout.buffer on py3 so we can write bytes to it since the plaintext
+ # of the vaulted object could be anything/binary/etc
+ output = getattr(sys.stdout, 'buffer', sys.stdout)
output.write(b_file_data)
else:
- if os.path.isfile(filename):
+ # file names are insecure and prone to race conditions, so remove and create securely
+ if os.path.isfile(thefile):
if shred:
- self._shred_file(filename)
+ self._shred_file(thefile)
else:
- os.remove(filename)
- with open(filename, "wb") as fh:
- fh.write(b_file_data)
+ os.remove(thefile)
+
+ # when setting new umask, we get previous as return
+ current_umask = os.umask(0o077)
+ try:
+ try:
+ # create file with secure permissions
+ fd = os.open(thefile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_TRUNC, mode)
+ except OSError as ose:
+ # Want to catch FileExistsError, which doesn't exist in Python 2, so catch OSError
+ # and compare the error number to get equivalent behavior in Python 2/3
+ if ose.errno == errno.EEXIST:
+ raise AnsibleError('Vault file got recreated while we were operating on it: %s' % to_native(ose))
+
+ raise AnsibleError('Problem creating temporary vault file: %s' % to_native(ose))
+
+ try:
+ # now write to the file and ensure ours is only data in it
+ os.ftruncate(fd, 0)
+ os.write(fd, b_file_data)
+ except OSError as e:
+ raise AnsibleError('Unable to write to temporary vault file: %s' % to_native(e))
+ finally:
+ # Make sure the file descriptor is always closed and reset umask
+ os.close(fd)
+ finally:
+ os.umask(current_umask)
def shuffle_files(self, src, dest):
prev = None
--
2.27.0

39
CVE-2020-1753.patch Normal file
View File

@ -0,0 +1,39 @@
From b75d6b7cc9c50184976589937c750cf3b265c08c Mon Sep 17 00:00:00 2001
From: Brian Coca <bcoca@users.noreply.github.com>
Date: Wed, 11 Mar 2020 11:50:48 -0400
Subject: [PATCH] warn about disclosure when using certain options
---
lib/ansible/plugins/connection/kubectl.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lib/ansible/plugins/connection/kubectl.py b/lib/ansible/plugins/connection/kubectl.py
index d8a0fcf..03f5487 100644
--- a/lib/ansible/plugins/connection/kubectl.py
+++ b/lib/ansible/plugins/connection/kubectl.py
@@ -65,6 +65,7 @@
kubectl_extra_args:
description:
- Extra arguments to pass to the kubectl command line.
+ - Please be aware that this passes information directly on the command line and it could expose sensitive data.
default: ''
vars:
- name: ansible_kubectl_extra_args
@@ -109,6 +110,8 @@
kubectl_password:
description:
- Provide a password for authenticating with the API.
+ - Please be aware that this passes information directly on the command line and it could expose sensitive data.
+ We recommend using the file based authentication options instead.
default: ''
vars:
- name: ansible_kubectl_password
@@ -117,6 +120,8 @@
kubectl_token:
description:
- API authentication bearer token.
+ - Please be aware that this passes information directly on the command line and it could expose sensitive data.
+ We recommend using the file based authentication options instead.
vars:
- name: ansible_kubectl_token
- name: ansible_kubectl_api_key

68
CVE-2021-20191.patch Normal file
View File

@ -0,0 +1,68 @@
From 881fde464fd8065021fd2792708e6e44ede37523 Mon Sep 17 00:00:00 2001
From: NilashishC <nilashishchakraborty8@gmail.com>
Date: Sun, 17 Jan 2021 17:47:09 +0530
Subject: [PATCH] Enable no_log for sensitive parameters in argspec
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
---
lib/ansible/modules/network/nxos/nxos_aaa_server.py | 2 +-
lib/ansible/modules/network/nxos/nxos_pim_interface.py | 2 +-
lib/ansible/modules/network/nxos/nxos_snmp_user.py | 2 +-
lib/ansible/modules/network/nxos/nxos_vrrp.py | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/ansible/modules/network/nxos/nxos_aaa_server.py b/lib/ansible/modules/network/nxos/nxos_aaa_server.py
index 6d705a4..7f421d0 100644
--- a/lib/ansible/modules/network/nxos/nxos_aaa_server.py
+++ b/lib/ansible/modules/network/nxos/nxos_aaa_server.py
@@ -241,7 +241,7 @@ def default_aaa_server(existing, params, server_type):
def main():
argument_spec = dict(
server_type=dict(type='str', choices=['radius', 'tacacs'], required=True),
- global_key=dict(type='str'),
+ global_key=dict(type="str", no_log=True),
encrypt_type=dict(type='str', choices=['0', '7']),
deadtime=dict(type='str'),
server_timeout=dict(type='str'),
diff --git a/lib/ansible/modules/network/nxos/nxos_pim_interface.py b/lib/ansible/modules/network/nxos/nxos_pim_interface.py
index fd2b17c..1b9bf0d 100644
--- a/lib/ansible/modules/network/nxos/nxos_pim_interface.py
+++ b/lib/ansible/modules/network/nxos/nxos_pim_interface.py
@@ -458,7 +458,7 @@ def main():
interface=dict(required=True),
sparse=dict(type='bool', default=False),
dr_prio=dict(type='str'),
- hello_auth_key=dict(type='str'),
+ hello_auth_key=dict(type="str", no_log=True),
hello_interval=dict(type='int'),
jp_policy_out=dict(type='str'),
jp_policy_in=dict(type='str'),
diff --git a/lib/ansible/modules/network/nxos/nxos_snmp_user.py b/lib/ansible/modules/network/nxos/nxos_snmp_user.py
index 4264197..a9fcc43 100644
--- a/lib/ansible/modules/network/nxos/nxos_snmp_user.py
+++ b/lib/ansible/modules/network/nxos/nxos_snmp_user.py
@@ -245,7 +245,7 @@ def main():
argument_spec = dict(
user=dict(required=True, type='str'),
group=dict(type='str'),
- pwd=dict(type='str'),
+ pwd=dict(type="str", no_log=True),
privacy=dict(type='str'),
authentication=dict(choices=['md5', 'sha']),
encrypt=dict(type='bool'),
diff --git a/lib/ansible/modules/network/nxos/nxos_vrrp.py b/lib/ansible/modules/network/nxos/nxos_vrrp.py
index b53386e..96593f7 100644
--- a/lib/ansible/modules/network/nxos/nxos_vrrp.py
+++ b/lib/ansible/modules/network/nxos/nxos_vrrp.py
@@ -319,7 +319,7 @@ def main():
admin_state=dict(required=False, type='str',
choices=['shutdown', 'no shutdown'],
default='no shutdown'),
- authentication=dict(required=False, type='str'),
+ authentication=dict(required=False, type="str", no_log=True),
state=dict(choices=['absent', 'present'], required=False, default='present')
)
argument_spec.update(nxos_argument_spec)
--
2.27.0

View File

@ -3,12 +3,23 @@
Name: ansible
Summary: SSH-based configuration management, deployment, and task execution system
Version: 2.5.5
Release: 1
Release: 2
License: Python-2.0 and MIT and GPL+
Url: http://ansible.com
Source0: https://releases.ansible.com/ansible/%{name}-%{version}.tar.gz
Patch0: 0001-Changes-to-support-building-docs-with-old-jinja2.patch
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
Patch105: CVE-2020-1736.patch
Patch106: CVE-2020-1737.patch
Patch107: CVE-2020-1738.patch
Patch108: CVE-2020-1739.patch
Patch109: CVE-2020-1740.patch
Patch110: CVE-2020-1753.patch
Patch111: CVE-2021-20191.patch
BuildArch: noarch
Provides: ansible-fireball = %{version}-%{release}
Obsoletes: ansible-fireball < 1.2.4
@ -63,6 +74,18 @@ This package installs extensive documentation for ansible
%setup -q
%patch0 -p1
%patch100 -p1
%patch101 -p1
%patch102 -p1
%patch103 -p1
%patch104 -p1
%patch105 -p1
%patch106 -p1
%patch107 -p1
%patch108 -p1
%patch109 -p1
%patch110 -p1
%patch111 -p1
%if 0%{?with_python3}
rm -rf %{py3dir}
cp -a . %{py3dir}
@ -123,5 +146,8 @@ cp -pr docs/docsite/rst .
%endif
%changelog
* Fri Sep 17 2021 yaoxin <yaoxin30@huawei.com> - 2.5.5-2
- Fix CVE-2019-14904 CVE-2020-10684 CVE-2020-10729 CVE-2020-1735-to-CVE-2020-1740 CVE-2020-1753 CVE-2021-20191
* Tue Jan 12 2021 yanan li <liyanan32@huawei.com> - 2.5.5-1
- Package init