CVE-2020-10685
Description
A flaw was found in Ansible Engine affecting Ansible Engine versions 2.7.x before 2.7.17 and 2.8.x before 2.8.11 and 2.9.x before 2.9.7 as well as Ansible Tower before and including versions 3.4.5 and 3.5.5 and 3.6.3 when using modules which decrypts vault files such as assemble, script, unarchive, win_copy, aws_s3 or copy modules. The temporary directory is created in /tmp leaves the s ts unencrypted. On Operating Systems which /tmp is not a tmpfs but part of the root partition, the directory is only cleared on boot and the decryp emains when the host is switched off. The system will be vulnerable when the system is not running. So decrypted data must be cleared as soon as possible and the data which normally is encrypted ble.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ansiblePyPI | >= 2.7.0a1, < 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
34e1fe80e681ffix vault temp file handling (#68433)
8 files changed · +72 −2
changelogs/fragments/vault_tmp_file.yml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: + - Ensure DataLoader temp files are removed at appropriate times and that we observe the LOCAL_TMP setting.
lib/ansible/executor/process/worker.py+11 −0 modified@@ -90,6 +90,10 @@ def __init__(self, final_q, task_vars, host, task, play_context, loader, variabl # set to /dev/null self._new_stdin = os.devnull + # NOTE: this works due to fork, if switching to threads this should change to per thread storage of temp files + # clear var to ensure we only delete files for this child + self._loader._tempfiles = set() + def run(self): ''' Called when the process is started. Pushes the result onto the @@ -159,6 +163,8 @@ def run(self): except: display.debug(u"WORKER EXCEPTION: %s" % to_text(e)) display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc())) + finally: + self._clean_up() display.debug("WORKER PROCESS EXITING") @@ -169,3 +175,8 @@ def run(self): # ps.print_stats() # with open('worker_%06d.stats' % os.getpid(), 'w') as f: # f.write(s.getvalue()) + + def _clean_up(self): + # NOTE: see note in init about forks + # ensure we cleanup all temp files for this worker + self._loader.cleanup_all_tmp_files()
lib/ansible/parsing/dataloader.py+13 −1 modified@@ -54,8 +54,16 @@ class DataLoader: ''' def __init__(self): + self._basedir = '.' + + # NOTE: not effective with forks as the main copy does not get updated. + # avoids rereading files self._FILE_CACHE = dict() + + # NOTE: not thread safe, also issues with forks not returning data to main proc + # so they need to be cleaned independantly. See WorkerProcess for example. + # used to keep track of temp files for cleaning self._tempfiles = set() # initialize the vault stuff with an empty password @@ -325,7 +333,7 @@ def path_dwim_relative_stack(self, paths, dirname, source, is_role=False): def _create_content_tempfile(self, content): ''' Create a tempfile containing defined content ''' - fd, content_tempfile = tempfile.mkstemp() + fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP) f = os.fdopen(fd, 'wb') content = to_bytes(content) try: @@ -388,6 +396,10 @@ def cleanup_tmp_file(self, file_path): self._tempfiles.remove(file_path) def cleanup_all_tmp_files(self): + """ + Removes all temporary files that DataLoader has created + NOTE: not thread safe, forks also need special handling see __init__ for details. + """ for f in self._tempfiles: try: self.cleanup_tmp_file(f)
lib/ansible/parsing/vault/__init__.py+1 −1 modified@@ -850,7 +850,7 @@ def _edit_file_helper(self, filename, secret, # Create a tempfile root, ext = os.path.splitext(os.path.realpath(filename)) - fd, tmp_path = tempfile.mkstemp(suffix=ext) + fd, tmp_path = tempfile.mkstemp(suffix=ext, dir=C.DEFAULT_LOCAL_TMP) os.close(fd) cmd = self._editor_shell_command(tmp_path)
test/integration/targets/vault/files/test_assemble/nonsecret.txt+1 −0 added@@ -0,0 +1 @@ +THIS IS OK
test/integration/targets/vault/files/test_assemble/secret.vault+7 −0 added@@ -0,0 +1,7 @@ +$ANSIBLE_VAULT;1.1;AES256 +37626439373465656332623633333336353334326531333666363766303339336134313136616165 +6561333963343739386334653636393363396366396338660a663537666561643862343233393265 +33336436633864323935356337623861663631316530336532633932623635346364363338363437 +3365313831366365350a613934313862313538626130653539303834656634353132343065633162 +34316135313837623735653932663139353164643834303534346238386435373832366564646236 +3461333465343434666639373432366139363566303564643066
test/integration/targets/vault/runme.sh+3 −0 modified@@ -487,3 +487,6 @@ ansible-playbook "$@" -i invalid_format/inventory --vault-id invalid_format/vaul EXPECTED_ERROR='Vault format unhexlify error: Odd-length string' ansible-playbook "$@" -i invalid_format/inventory --vault-id invalid_format/vault-secret invalid_format/broken-group-vars-tasks.yml 2>&1 | grep "${EXPECTED_ERROR}" + +# Ensure we don't leave unencrypted temp files dangling +ansible-playbook -v "$@" --vault-password-file vault-password test_dangling_temp.yml
test/integration/targets/vault/test_dangling_temp.yml+34 −0 added@@ -0,0 +1,34 @@ +- hosts: localhost + gather_facts: False + vars: + od: "{{output_dir|default('/tmp')}}/test_vault_assemble" + tasks: + - name: create target directory + file: + path: "{{od}}" + state: directory + + - name: assemble_file file with secret + assemble: + src: files/test_assemble + dest: "{{od}}/dest_file" + remote_src: no + mode: 0600 + + - name: remove assembled file with secret (so nothing should have unencrypted secret) + file: path="{{od}}/dest_file" state=absent + + - name: find temp files with secrets + find: + paths: '{{temp_paths}}' + contains: 'VAULT TEST IN WHICH BAD THING HAPPENED' + recurse: yes + register: badthings + vars: + temp_paths: "{{[lookup('env', 'TMP'), lookup('env', 'TEMP'), hardcoded]|flatten(1)|unique|list}}" + hardcoded: ['/tmp', '/var/tmp'] + + - name: ensure we failed to find any + assert: + that: + - badthings['matched'] == 0
51d251475354fix vault temp file handling (#68433)
8 files changed · +73 −2
changelogs/fragments/vault_tmp_file.yml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: + - Ensure DataLoader temp files are removed at appropriate times and that we observe the LOCAL_TMP setting.
lib/ansible/executor/process/worker.py+11 −0 modified@@ -67,6 +67,10 @@ def __init__(self, final_q, task_vars, host, task, play_context, loader, variabl self._variable_manager = variable_manager self._shared_loader_obj = shared_loader_obj + # NOTE: this works due to fork, if switching to threads this should change to per thread storage of temp files + # clear var to ensure we only delete files for this child + self._loader._tempfiles = set() + def _save_stdin(self): self._new_stdin = os.devnull try: @@ -200,6 +204,8 @@ def _run(self): except Exception: display.debug(u"WORKER EXCEPTION: %s" % to_text(e)) display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc())) + finally: + self._clean_up() display.debug("WORKER PROCESS EXITING") @@ -210,3 +216,8 @@ def _run(self): # ps.print_stats() # with open('worker_%06d.stats' % os.getpid(), 'w') as f: # f.write(s.getvalue()) + + def _clean_up(self): + # NOTE: see note in init about forks + # ensure we cleanup all temp files for this worker + self._loader.cleanup_all_tmp_files()
lib/ansible/parsing/dataloader.py+13 −1 modified@@ -51,8 +51,16 @@ class DataLoader: ''' def __init__(self): + self._basedir = '.' + + # NOTE: not effective with forks as the main copy does not get updated. + # avoids rereading files self._FILE_CACHE = dict() + + # NOTE: not thread safe, also issues with forks not returning data to main proc + # so they need to be cleaned independantly. See WorkerProcess for example. + # used to keep track of temp files for cleaning self._tempfiles = set() # initialize the vault stuff with an empty password @@ -322,7 +330,7 @@ def path_dwim_relative_stack(self, paths, dirname, source, is_role=False): def _create_content_tempfile(self, content): ''' Create a tempfile containing defined content ''' - fd, content_tempfile = tempfile.mkstemp() + fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP) f = os.fdopen(fd, 'wb') content = to_bytes(content) try: @@ -385,6 +393,10 @@ def cleanup_tmp_file(self, file_path): self._tempfiles.remove(file_path) def cleanup_all_tmp_files(self): + """ + Removes all temporary files that DataLoader has created + NOTE: not thread safe, forks also need special handling see __init__ for details. + """ for f in self._tempfiles: try: self.cleanup_tmp_file(f)
lib/ansible/parsing/vault/__init__.py+1 −1 modified@@ -850,7 +850,7 @@ def _edit_file_helper(self, filename, secret, # Create a tempfile root, ext = os.path.splitext(os.path.realpath(filename)) - fd, tmp_path = tempfile.mkstemp(suffix=ext) + fd, tmp_path = tempfile.mkstemp(suffix=ext, dir=C.DEFAULT_LOCAL_TMP) os.close(fd) cmd = self._editor_shell_command(tmp_path)
test/integration/targets/vault/files/test_assemble/nonsecret.txt+1 −0 added@@ -0,0 +1 @@ +THIS IS OK
test/integration/targets/vault/files/test_assemble/secret.vault+7 −0 added@@ -0,0 +1,7 @@ +$ANSIBLE_VAULT;1.1;AES256 +37626439373465656332623633333336353334326531333666363766303339336134313136616165 +6561333963343739386334653636393363396366396338660a663537666561643862343233393265 +33336436633864323935356337623861663631316530336532633932623635346364363338363437 +3365313831366365350a613934313862313538626130653539303834656634353132343065633162 +34316135313837623735653932663139353164643834303534346238386435373832366564646236 +3461333465343434666639373432366139363566303564643066
test/integration/targets/vault/runme.sh+4 −0 modified@@ -507,3 +507,7 @@ ansible-playbook "$@" -i invalid_format/inventory --vault-id invalid_format/vaul # Run playbook with vault file with unicode in filename (https://github.com/ansible/ansible/issues/50316) ansible-playbook -i ../../inventory -v "$@" --vault-password-file vault-password test_utf8_value_in_filename.yml + +# Ensure we don't leave unencrypted temp files dangling +ansible-playbook -v "$@" --vault-password-file vault-password test_dangling_temp.yml +
test/integration/targets/vault/test_dangling_temp.yml+34 −0 added@@ -0,0 +1,34 @@ +- hosts: localhost + gather_facts: False + vars: + od: "{{output_dir|default('/tmp')}}/test_vault_assemble" + tasks: + - name: create target directory + file: + path: "{{od}}" + state: directory + + - name: assemble_file file with secret + assemble: + src: files/test_assemble + dest: "{{od}}/dest_file" + remote_src: no + mode: 0600 + + - name: remove assembled file with secret (so nothing should have unencrypted secret) + file: path="{{od}}/dest_file" state=absent + + - name: find temp files with secrets + find: + paths: '{{temp_paths}}' + contains: 'VAULT TEST IN WHICH BAD THING HAPPENED' + recurse: yes + register: badthings + vars: + temp_paths: "{{[lookup('env', 'TMP'), lookup('env', 'TEMP'), hardcoded]|flatten(1)|unique|list}}" + hardcoded: ['/tmp', '/var/tmp'] + + - name: ensure we failed to find any + assert: + that: + - badthings['matched'] == 0
e1273b6faf03fix vault temp file handling (#68433)
8 files changed · +73 −2
changelogs/fragments/vault_tmp_file.yml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: + - Ensure DataLoader temp files are removed at appropriate times and that we observe the LOCAL_TMP setting.
lib/ansible/executor/process/worker.py+11 −0 modified@@ -67,6 +67,10 @@ def __init__(self, final_q, task_vars, host, task, play_context, loader, variabl self._variable_manager = variable_manager self._shared_loader_obj = shared_loader_obj + # NOTE: this works due to fork, if switching to threads this should change to per thread storage of temp files + # clear var to ensure we only delete files for this child + self._loader._tempfiles = set() + def _save_stdin(self): self._new_stdin = os.devnull try: @@ -200,6 +204,8 @@ def _run(self): except Exception: display.debug(u"WORKER EXCEPTION: %s" % to_text(e)) display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc())) + finally: + self._clean_up() display.debug("WORKER PROCESS EXITING") @@ -210,3 +216,8 @@ def _run(self): # ps.print_stats() # with open('worker_%06d.stats' % os.getpid(), 'w') as f: # f.write(s.getvalue()) + + def _clean_up(self): + # NOTE: see note in init about forks + # ensure we cleanup all temp files for this worker + self._loader.cleanup_all_tmp_files()
lib/ansible/parsing/dataloader.py+13 −1 modified@@ -51,8 +51,16 @@ class DataLoader: ''' def __init__(self): + self._basedir = '.' + + # NOTE: not effective with forks as the main copy does not get updated. + # avoids rereading files self._FILE_CACHE = dict() + + # NOTE: not thread safe, also issues with forks not returning data to main proc + # so they need to be cleaned independantly. See WorkerProcess for example. + # used to keep track of temp files for cleaning self._tempfiles = set() # initialize the vault stuff with an empty password @@ -322,7 +330,7 @@ def path_dwim_relative_stack(self, paths, dirname, source, is_role=False): def _create_content_tempfile(self, content): ''' Create a tempfile containing defined content ''' - fd, content_tempfile = tempfile.mkstemp() + fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP) f = os.fdopen(fd, 'wb') content = to_bytes(content) try: @@ -385,6 +393,10 @@ def cleanup_tmp_file(self, file_path): self._tempfiles.remove(file_path) def cleanup_all_tmp_files(self): + """ + Removes all temporary files that DataLoader has created + NOTE: not thread safe, forks also need special handling see __init__ for details. + """ for f in self._tempfiles: try: self.cleanup_tmp_file(f)
lib/ansible/parsing/vault/__init__.py+1 −1 modified@@ -848,7 +848,7 @@ def _edit_file_helper(self, filename, secret, # Create a tempfile root, ext = os.path.splitext(os.path.realpath(filename)) - fd, tmp_path = tempfile.mkstemp(suffix=ext) + fd, tmp_path = tempfile.mkstemp(suffix=ext, dir=C.DEFAULT_LOCAL_TMP) os.close(fd) cmd = self._editor_shell_command(tmp_path)
test/integration/targets/vault/files/test_assemble/nonsecret.txt+1 −0 added@@ -0,0 +1 @@ +THIS IS OK
test/integration/targets/vault/files/test_assemble/secret.vault+7 −0 added@@ -0,0 +1,7 @@ +$ANSIBLE_VAULT;1.1;AES256 +37626439373465656332623633333336353334326531333666363766303339336134313136616165 +6561333963343739386334653636393363396366396338660a663537666561643862343233393265 +33336436633864323935356337623861663631316530336532633932623635346364363338363437 +3365313831366365350a613934313862313538626130653539303834656634353132343065633162 +34316135313837623735653932663139353164643834303534346238386435373832366564646236 +3461333465343434666639373432366139363566303564643066
test/integration/targets/vault/runme.sh+4 −0 modified@@ -507,3 +507,7 @@ ansible-playbook "$@" -i invalid_format/inventory --vault-id invalid_format/vaul # Run playbook with vault file with unicode in filename (https://github.com/ansible/ansible/issues/50316) ansible-playbook -i ../../inventory -v "$@" --vault-password-file vault-password test_utf8_value_in_filename.yml + +# Ensure we don't leave unencrypted temp files dangling +ansible-playbook -v "$@" --vault-password-file vault-password test_dangling_temp.yml +
test/integration/targets/vault/test_dangling_temp.yml+34 −0 added@@ -0,0 +1,34 @@ +- hosts: localhost + gather_facts: False + vars: + od: "{{output_dir|default('/tmp')}}/test_vault_assemble" + tasks: + - name: create target directory + file: + path: "{{od}}" + state: directory + + - name: assemble_file file with secret + assemble: + src: files/test_assemble + dest: "{{od}}/dest_file" + remote_src: no + mode: 0600 + + - name: remove assembled file with secret (so nothing should have unencrypted secret) + file: path="{{od}}/dest_file" state=absent + + - name: find temp files with secrets + find: + paths: '{{temp_paths}}' + contains: 'VAULT TEST IN WHICH BAD THING HAPPENED' + recurse: yes + register: badthings + vars: + temp_paths: "{{[lookup('env', 'TMP'), lookup('env', 'TEMP'), hardcoded]|flatten(1)|unique|list}}" + hardcoded: ['/tmp', '/var/tmp'] + + - name: ensure we failed to find any + assert: + that: + - badthings['matched'] == 0
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
10- github.com/advisories/GHSA-77g3-3j5w-64w4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-10685ghsaADVISORY
- security.gentoo.org/glsa/202006-11ghsavendor-advisoryWEB
- www.debian.org/security/2021/dsa-4950ghsavendor-advisoryWEB
- bugzilla.redhat.com/show_bug.cgighsaWEB
- github.com/ansible/ansible/commit/4e1fe80e681fa466626e9dea53efe6b0253ea1b2ghsaWEB
- github.com/ansible/ansible/commit/51d2514753544a9d58cd7524e27e696b2c944fb5ghsaWEB
- github.com/ansible/ansible/commit/e1273b6faf036ed84e4f4edee85b888a4e256aeeghsaWEB
- github.com/ansible/ansible/pull/68433ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/ansible/PYSEC-2020-1.yamlghsaWEB
News mentions
0No linked articles in our index yet.