CVE-2020-1733
Description
A race condition flaw was found in Ansible Engine 2.7.17 and prior, 2.8.9 and prior, 2.9.6 and prior when running a playbook with an unprivileged become user. When Ansible needs to run a module with become user, the temporary directory is created in /var/tmp. This directory is created with "umask 77 && mkdir -p <dir>"; this operation does not fail if the directory already exists and is owned by another user. An attacker could take advantage to gain control of the become user as the target directory can be retrieved by iterating '/proc/<pid>/cmdline'.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ansiblePyPI | < 2.7.17 | 2.7.17 |
ansiblePyPI | >= 2.8.0a1, < 2.8.11 | 2.8.11 |
ansiblePyPI | >= 2.9.0a1, < 2.9.7 | 2.9.7 |
Affected products
1Patches
3ecf99d5e1ff7avoid mkdir -p (#68921) (#68928)
4 files changed · +26 −11
changelogs/fragments/remote_mkdir_fix.yml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: + - Ensure we get an error when creating a remote tmp if it already exists. CVE-2020-1733
lib/ansible/plugins/action/__init__.py+11 −7 modified@@ -276,10 +276,6 @@ def _make_tmp_path(self, remote_user=None): ''' become_unprivileged = self._is_become_unprivileged() - try: - remote_tmp = self._connection._shell.get_option('remote_tmp') - except AnsibleError: - remote_tmp = '~/.ansible/tmp' # deal with tmpdir creation basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) @@ -289,7 +285,15 @@ def _make_tmp_path(self, remote_user=None): if getattr(self._connection, '_remote_is_local', False): tmpdir = C.DEFAULT_LOCAL_TMP else: - tmpdir = self._remote_expand_user(remote_tmp, sudoable=False) + # NOTE: shell plugins should populate this setting anyways, but they dont do remote expansion, which + # we need for 'non posix' systems like cloud-init and solaris + try: + tmpdir = self._connection._shell.get_option('remote_tmp') + except AnsibleError: + tmpdir = '~/.ansible/tmp' + tmpdir = self._remote_expand_user(tmpdir, sudoable=False) + + basefile = self._connection._shell._generate_temp_dir_name() cmd = self._connection._shell.mkdtemp(basefile=basefile, system=become_unprivileged, tmpdir=tmpdir) result = self._low_level_execute_command(cmd, sudoable=False) @@ -308,9 +312,9 @@ def _make_tmp_path(self, remote_user=None): elif u'No space left on device' in result['stderr']: output = result['stderr'] else: - output = ('Authentication or permission failure. ' + output = ('Failed to create temporary directory.' 'In some cases, you may have been able to authenticate and did not have permissions on the target directory. ' - 'Consider changing the remote tmp path in ansible.cfg to a path rooted in "/tmp". ' + 'Consider changing the remote tmp path in ansible.cfg to a path rooted in "/tmp", for more error information use -vvv. ' 'Failed command was: %s, exited with result %d' % (cmd, result['rc'])) if 'stdout' in result and result['stdout'] != u'': output = output + u", stdout output: %s" % result['stdout']
lib/ansible/plugins/shell/__init__.py+11 −4 modified@@ -23,7 +23,7 @@ import re import time -import ansible.constants as C +from ansible import constants as C from ansible.errors import AnsibleError from ansible.module_utils.six import text_type from ansible.module_utils.six.moves import shlex_quote @@ -76,6 +76,10 @@ def set_options(self, task_keys=None, var_options=None, direct=None): except AnsibleError: pass + @staticmethod + def _generate_temp_dir_name(): + return 'ansible-tmp-%s-%s-%s' % (time.time(), os.getpid(), random.randint(0, 2**48)) + def env_prefix(self, **kwargs): return ' '.join(['%s=%s' % (k, shlex_quote(text_type(v))) for k, v in kwargs.items()]) @@ -125,7 +129,7 @@ def exists(self, path): def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): if not basefile: - basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) + basefile = self.__class__._generate_temp_dir_name() # When system is specified we have to create this in a directory where # other users can read and access the tmp directory. @@ -137,7 +141,8 @@ def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): # passed in tmpdir if it is valid or the first one from the setting if not. if system: - tmpdir = tmpdir.rstrip('/') + if tmpdir: + tmpdir = tmpdir.rstrip('/') if tmpdir in self.get_option('system_tmpdirs'): basetmpdir = tmpdir @@ -151,7 +156,9 @@ def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): basetmp = self.join_path(basetmpdir, basefile) - cmd = 'mkdir -p %s echo %s %s' % (self._SHELL_SUB_LEFT, basetmp, self._SHELL_SUB_RIGHT) + # use mkdir -p to ensure parents exist, but mkdir fullpath to ensure last one is created by us + cmd = 'mkdir -p %s echo %s %s' % (self._SHELL_SUB_LEFT, basetmpdir, self._SHELL_SUB_RIGHT) + cmd += '%s mkdir %s' % (self._SHELL_AND, basetmp) cmd += ' %s echo %s=%s echo %s %s' % (self._SHELL_AND, basefile, self._SHELL_SUB_LEFT, basetmp, self._SHELL_SUB_RIGHT) # change the umask in a subshell to achieve the desired mode
lib/ansible/plugins/shell/powershell.py+2 −0 modified@@ -1574,6 +1574,8 @@ def remove(self, path, recurse=False): def mkdtemp(self, basefile=None, system=False, mode=None, tmpdir=None): # Windows does not have an equivalent for the system temp files, so # the param is ignored + if not basefile: + basefile = self.__class__._generate_temp_dir_name() basefile = self._escape(self._unquote(basefile)) basetmpdir = tmpdir if tmpdir else self.get_option('remote_tmp')
8251d9f4c2bcavoid mkdir -p (#68921) (#68927)
4 files changed · +23 −12
changelogs/fragments/remote_mkdir_fix.yml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: + - Ensure we get an error when creating a remote tmp if it already exists. CVE-2020-1733
lib/ansible/plugins/action/__init__.py+8 −8 modified@@ -326,18 +326,18 @@ def _make_tmp_path(self, remote_user=None): Create and return a temporary path on a remote box. ''' - become_unprivileged = self._is_become_unprivileged() - remote_tmp = self.get_shell_option('remote_tmp', default='~/.ansible/tmp') - - # deal with tmpdir creation - basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) # Network connection plugins (network_cli, netconf, etc.) execute on the controller, rather than the remote host. # As such, we want to avoid using remote_user for paths as remote_user may not line up with the local user # This is a hack and should be solved by more intelligent handling of remote_tmp in 2.7 if getattr(self._connection, '_remote_is_local', False): tmpdir = C.DEFAULT_LOCAL_TMP else: - tmpdir = self._remote_expand_user(remote_tmp, sudoable=False) + # NOTE: shell plugins should populate this setting anyways, but they dont do remote expansion, which + # we need for 'non posix' systems like cloud-init and solaris + tmpdir = self._remote_expand_user(self.get_shell_option('remote_tmp', default='~/.ansible/tmp'), sudoable=False) + + become_unprivileged = self._is_become_unprivileged() + basefile = self._connection._shell._generate_temp_dir_name() cmd = self._connection._shell.mkdtemp(basefile=basefile, system=become_unprivileged, tmpdir=tmpdir) result = self._low_level_execute_command(cmd, sudoable=False) @@ -356,9 +356,9 @@ def _make_tmp_path(self, remote_user=None): elif u'No space left on device' in result['stderr']: output = result['stderr'] else: - output = ('Authentication or permission failure. ' + output = ('Failed to create temporary directory.' 'In some cases, you may have been able to authenticate and did not have permissions on the target directory. ' - 'Consider changing the remote tmp path in ansible.cfg to a path rooted in "/tmp". ' + 'Consider changing the remote tmp path in ansible.cfg to a path rooted in "/tmp", for more error information use -vvv. ' 'Failed command was: %s, exited with result %d' % (cmd, result['rc'])) if 'stdout' in result and result['stdout'] != u'': output = output + u", stdout output: %s" % result['stdout']
lib/ansible/plugins/shell/__init__.py+11 −4 modified@@ -23,7 +23,7 @@ import re import time -import ansible.constants as C +from ansible import constants as C from ansible.errors import AnsibleError from ansible.module_utils.six import text_type from ansible.module_utils.six.moves import shlex_quote @@ -82,6 +82,10 @@ def set_options(self, task_keys=None, var_options=None, direct=None): except KeyError: pass + @staticmethod + def _generate_temp_dir_name(): + return 'ansible-tmp-%s-%s-%s' % (time.time(), os.getpid(), random.randint(0, 2**48)) + def env_prefix(self, **kwargs): return ' '.join(['%s=%s' % (k, shlex_quote(text_type(v))) for k, v in kwargs.items()]) @@ -131,7 +135,7 @@ def exists(self, path): def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): if not basefile: - basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) + basefile = self.__class__._generate_temp_dir_name() # When system is specified we have to create this in a directory where # other users can read and access the tmp directory. @@ -143,7 +147,8 @@ def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): # passed in tmpdir if it is valid or the first one from the setting if not. if system: - tmpdir = tmpdir.rstrip('/') + if tmpdir: + tmpdir = tmpdir.rstrip('/') if tmpdir in self.get_option('system_tmpdirs'): basetmpdir = tmpdir @@ -157,7 +162,9 @@ def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): basetmp = self.join_path(basetmpdir, basefile) - cmd = 'mkdir -p %s echo %s %s' % (self._SHELL_SUB_LEFT, basetmp, self._SHELL_SUB_RIGHT) + # use mkdir -p to ensure parents exist, but mkdir fullpath to ensure last one is created by us + cmd = 'mkdir -p %s echo %s %s' % (self._SHELL_SUB_LEFT, basetmpdir, self._SHELL_SUB_RIGHT) + cmd += '%s mkdir %s' % (self._SHELL_AND, basetmp) cmd += ' %s echo %s=%s echo %s %s' % (self._SHELL_AND, basefile, self._SHELL_SUB_LEFT, basetmp, self._SHELL_SUB_RIGHT) # change the umask in a subshell to achieve the desired mode
lib/ansible/plugins/shell/powershell.py+2 −0 modified@@ -135,6 +135,8 @@ def remove(self, path, recurse=False): def mkdtemp(self, basefile=None, system=False, mode=None, tmpdir=None): # Windows does not have an equivalent for the system temp files, so # the param is ignored + if not basefile: + basefile = self.__class__._generate_temp_dir_name() basefile = self._escape(self._unquote(basefile)) basetmpdir = tmpdir if tmpdir else self.get_option('remote_tmp')
4 files changed · +22 −12
changelogs/fragments/remote_mkdir_fix.yml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: + - Ensure we get an error when creating a remote tmp if it already exists. CVE-2020-1733
lib/ansible/plugins/action/__init__.py+8 −8 modified@@ -332,18 +332,18 @@ def _make_tmp_path(self, remote_user=None): Create and return a temporary path on a remote box. ''' - become_unprivileged = self._is_become_unprivileged() - remote_tmp = self.get_shell_option('remote_tmp', default='~/.ansible/tmp') - - # deal with tmpdir creation - basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) # Network connection plugins (network_cli, netconf, etc.) execute on the controller, rather than the remote host. # As such, we want to avoid using remote_user for paths as remote_user may not line up with the local user # This is a hack and should be solved by more intelligent handling of remote_tmp in 2.7 if getattr(self._connection, '_remote_is_local', False): tmpdir = C.DEFAULT_LOCAL_TMP else: - tmpdir = self._remote_expand_user(remote_tmp, sudoable=False) + # NOTE: shell plugins should populate this setting anyways, but they dont do remote expansion, which + # we need for 'non posix' systems like cloud-init and solaris + tmpdir = self._remote_expand_user(self.get_shell_option('remote_tmp', default='~/.ansible/tmp'), sudoable=False) + + become_unprivileged = self._is_become_unprivileged() + basefile = self._connection._shell._generate_temp_dir_name() cmd = self._connection._shell.mkdtemp(basefile=basefile, system=become_unprivileged, tmpdir=tmpdir) result = self._low_level_execute_command(cmd, sudoable=False) @@ -362,9 +362,9 @@ def _make_tmp_path(self, remote_user=None): elif u'No space left on device' in result['stderr']: output = result['stderr'] else: - output = ('Authentication or permission failure. ' + output = ('Failed to create temporary directory.' 'In some cases, you may have been able to authenticate and did not have permissions on the target directory. ' - 'Consider changing the remote tmp path in ansible.cfg to a path rooted in "/tmp". ' + 'Consider changing the remote tmp path in ansible.cfg to a path rooted in "/tmp", for more error information use -vvv. ' 'Failed command was: %s, exited with result %d' % (cmd, result['rc'])) if 'stdout' in result and result['stdout'] != u'': output = output + u", stdout output: %s" % result['stdout']
lib/ansible/plugins/shell/__init__.py+10 −4 modified@@ -23,7 +23,6 @@ import re import time -import ansible.constants as C from ansible.errors import AnsibleError from ansible.module_utils.six import text_type from ansible.module_utils.six.moves import shlex_quote @@ -76,6 +75,10 @@ def set_options(self, task_keys=None, var_options=None, direct=None): except KeyError: pass + @staticmethod + def _generate_temp_dir_name(): + return 'ansible-tmp-%s-%s-%s' % (time.time(), os.getpid(), random.randint(0, 2**48)) + def env_prefix(self, **kwargs): return ' '.join(['%s=%s' % (k, shlex_quote(text_type(v))) for k, v in kwargs.items()]) @@ -125,7 +128,7 @@ def exists(self, path): def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): if not basefile: - basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) + basefile = self.__class__._generate_temp_dir_name() # When system is specified we have to create this in a directory where # other users can read and access the tmp directory. @@ -137,7 +140,8 @@ def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): # passed in tmpdir if it is valid or the first one from the setting if not. if system: - tmpdir = tmpdir.rstrip('/') + if tmpdir: + tmpdir = tmpdir.rstrip('/') if tmpdir in self.get_option('system_tmpdirs'): basetmpdir = tmpdir @@ -151,7 +155,9 @@ def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None): basetmp = self.join_path(basetmpdir, basefile) - cmd = 'mkdir -p %s echo %s %s' % (self._SHELL_SUB_LEFT, basetmp, self._SHELL_SUB_RIGHT) + # use mkdir -p to ensure parents exist, but mkdir fullpath to ensure last one is created by us + cmd = 'mkdir -p %s echo %s %s' % (self._SHELL_SUB_LEFT, basetmpdir, self._SHELL_SUB_RIGHT) + cmd += '%s mkdir %s' % (self._SHELL_AND, basetmp) cmd += ' %s echo %s=%s echo %s %s' % (self._SHELL_AND, basefile, self._SHELL_SUB_LEFT, basetmp, self._SHELL_SUB_RIGHT) # change the umask in a subshell to achieve the desired mode
lib/ansible/plugins/shell/powershell.py+2 −0 modified@@ -135,6 +135,8 @@ def remove(self, path, recurse=False): def mkdtemp(self, basefile=None, system=False, mode=None, tmpdir=None): # Windows does not have an equivalent for the system temp files, so # the param is ignored + if not basefile: + basefile = self.__class__._generate_temp_dir_name() basefile = self._escape(self._unquote(basefile)) basetmpdir = tmpdir if tmpdir else self.get_option('remote_tmp')
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
17- github.com/advisories/GHSA-g4mq-6fp5-qwcfghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/DKPA4KC3OJSUFASUYMG66HKJE7ADNGFW/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/MRRYUU5ZBLPBXCYG6CFP35D64NP2UB2S/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/WQVOQD4VAIXXTVQAJKTN7NUGTJFE2PCB/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2020-1733ghsaADVISORY
- security.gentoo.org/glsa/202006-11ghsavendor-advisoryx_refsource_GENTOOWEB
- www.debian.org/security/2021/dsa-4950ghsavendor-advisoryx_refsource_DEBIANWEB
- bugzilla.redhat.com/show_bug.cgighsax_refsource_CONFIRMWEB
- github.com/ansible/ansible/commit/80b9a0a25c5f75e84aefc8f2b293fb1933b154f2ghsaWEB
- github.com/ansible/ansible/commit/8251d9f4c2bc82632ab992277fcd30ccbf87aa47ghsaWEB
- github.com/ansible/ansible/commit/ecf99d5e1ff732a7777010facd6c98bb0994605eghsaWEB
- github.com/ansible/ansible/issues/67791ghsax_refsource_MISCWEB
- github.com/pypa/advisory-database/tree/main/vulns/ansible/PYSEC-2020-5.yamlghsaWEB
- lists.debian.org/debian-lts-announce/2020/05/msg00005.htmlghsamailing-listx_refsource_MLISTWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DKPA4KC3OJSUFASUYMG66HKJE7ADNGFWghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MRRYUU5ZBLPBXCYG6CFP35D64NP2UB2SghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WQVOQD4VAIXXTVQAJKTN7NUGTJFE2PCBghsaWEB
News mentions
0No linked articles in our index yet.