Fix CVE-2024-8775 and CVE-2024-9902

(cherry picked from commit 60aeeac472e63256ee4fc7ccf7a626ac8dd1ea08)
This commit is contained in:
starlet-dx 2024-12-02 10:08:46 +08:00 committed by openeuler-sync-bot
parent ad1c971d1d
commit eff6eb882b
3 changed files with 640 additions and 1 deletions

488
CVE-2024-8775.patch Normal file
View File

@ -0,0 +1,488 @@
From: Matt Davis <6775756+nitzmahone@users.noreply.github.com>
Date: Thu, 24 Oct 2024 15:56:54 -0700
Subject: CVE-2024-8775 Preserve `_ansible_no_log` from action result;
fix `include_vars` to set properly (#84143)
* fixes for CVE-2024-8775
* propagate truthy `_ansible_no_log` in action result (previously superseded by task-calculated value)
* always mask entire `include_vars` action result if any file loaded had a false `show_content` flag (previously used only the flag value from the last file loaded)
* update no_log tests for CVE-2024-8775
* include validation of _ansible_no_log preservation when set by actions
* replace static values with dynamic for increased robustness to logging/display/callback changes (but still using grep counts :( )
* changelog
* use ternary, coerce to bool explicitly
[backport]
"grep -q" exits on the first match, so the preceding
process in the pipeline gets SIGPIPE. Changing "grep -q
'porter.*cable'" to "grep 'porter.*cable' >/dev/null" makes that test
pass.
origin: backport, https://github.com/ansible/ansible/commit/c9ac477e53a99e95781f333eec3329a935c1bf95
bug: https://github.com/ansible/ansible/issues/84217
bug-debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1086551
---
changelogs/fragments/cve-2024-8775.yml | 5 ++++
lib/ansible/executor/task_executor.py | 3 +-
lib/ansible/plugins/action/include_vars.py | 3 +-
.../targets/include_vars-ad-hoc/dir/encrypted.yml | 6 ++++
.../targets/include_vars-ad-hoc/runme.sh | 22 +++++++++++++--
.../targets/include_vars-ad-hoc/vaultpass | 3 ++
.../no_log/action_plugins/action_sets_no_log.py | 8 ++++++
.../targets/no_log/ansible_no_log_in_result.yml | 13 +++++++++
test/integration/targets/no_log/dynamic.yml | 29 +++++++++++++++-----
test/integration/targets/no_log/no_log_config.yml | 2 +-
test/integration/targets/no_log/no_log_local.yml | 15 ++++++----
.../targets/no_log/no_log_suboptions.yml | 14 +++++-----
.../targets/no_log/no_log_suboptions_invalid.yml | 29 +++++++++++---------
test/integration/targets/no_log/runme.sh | 16 +++++++----
test/integration/targets/no_log/runme.sh.orig | 26 ++++++++++++++++++
test/integration/targets/no_log/secretvars.yml | 32 ++++++++++++++++++++++
16 files changed, 182 insertions(+), 44 deletions(-)
create mode 100644 changelogs/fragments/cve-2024-8775.yml
create mode 100644 test/integration/targets/include_vars-ad-hoc/dir/encrypted.yml
create mode 100755 test/integration/targets/include_vars-ad-hoc/vaultpass
create mode 100644 test/integration/targets/no_log/action_plugins/action_sets_no_log.py
create mode 100644 test/integration/targets/no_log/ansible_no_log_in_result.yml
create mode 100755 test/integration/targets/no_log/runme.sh.orig
create mode 100644 test/integration/targets/no_log/secretvars.yml
diff --git a/changelogs/fragments/cve-2024-8775.yml b/changelogs/fragments/cve-2024-8775.yml
new file mode 100644
index 0000000..a292c99
--- /dev/null
+++ b/changelogs/fragments/cve-2024-8775.yml
@@ -0,0 +1,5 @@
+security_fixes:
+ - task result processing - Ensure that action-sourced result masking (``_ansible_no_log=True``)
+ is preserved. (CVE-2024-8775)
+ - include_vars action - Ensure that result masking is correctly requested when vault-encrypted
+ files are read. (CVE-2024-8775)
diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py
index 65bcacf..957d1bf 100644
--- a/lib/ansible/executor/task_executor.py
+++ b/lib/ansible/executor/task_executor.py
@@ -677,8 +677,9 @@ class TaskExecutor:
self._handler.cleanup()
display.debug("handler run complete")
+ # propagate no log to result- the action can set this, so only overwrite it with the task's value if missing or falsey
# preserve no log
- result["_ansible_no_log"] = self._play_context.no_log
+ result["_ansible_no_log"] = bool(self._play_context.no_log or result.get('_ansible_no_log', False))
# update the local copy of vars with the registered value, if specified,
# or any facts which may have been generated by the module execution
diff --git a/lib/ansible/plugins/action/include_vars.py b/lib/ansible/plugins/action/include_vars.py
index 0723453..bd67d01 100644
--- a/lib/ansible/plugins/action/include_vars.py
+++ b/lib/ansible/plugins/action/include_vars.py
@@ -225,7 +225,8 @@ class ActionModule(ActionBase):
b_data, show_content = self._loader._get_file_contents(filename)
data = to_text(b_data, errors='surrogate_or_strict')
- self.show_content = show_content
+ self.show_content &= show_content # mask all results if any file was encrypted
+
data = self._loader.load(data, file_name=filename, show_content=show_content)
if not data:
data = dict()
diff --git a/test/integration/targets/include_vars-ad-hoc/dir/encrypted.yml b/test/integration/targets/include_vars-ad-hoc/dir/encrypted.yml
new file mode 100644
index 0000000..328f180
--- /dev/null
+++ b/test/integration/targets/include_vars-ad-hoc/dir/encrypted.yml
@@ -0,0 +1,6 @@
+$ANSIBLE_VAULT;1.1;AES256
+31613539636636336264396235633933633839646337323533316638633336653461393036336664
+3939386435313638366366626566346135623932653238360a366261303663343034633865626132
+31646231623630333636383636383833656331643164656366623332396439306132663264663131
+6439633766376261320a616265306430366530363866356433366430633265353739373732646536
+37623661333064306162373463616231636365373231313939373230643936313362
diff --git a/test/integration/targets/include_vars-ad-hoc/runme.sh b/test/integration/targets/include_vars-ad-hoc/runme.sh
index 51b68d2..41a0929 100755
--- a/test/integration/targets/include_vars-ad-hoc/runme.sh
+++ b/test/integration/targets/include_vars-ad-hoc/runme.sh
@@ -1,6 +1,22 @@
#!/usr/bin/env bash
-set -eux
+set -eux -o pipefail
-ansible testhost -i ../../inventory -m include_vars -a 'dir/inc.yml' "$@"
-ansible testhost -i ../../inventory -m include_vars -a 'dir=dir' "$@"
+echo "single file include"
+ansible testhost -i ../../inventory -m include_vars -a 'dir/inc.yml' -vvv 2>&1 | grep 'porter.*cable' 2>&1
+
+echo "single file encrypted include"
+ansible testhost -i ../../inventory -m include_vars -a 'dir/encrypted.yml' -vvv --vault-password-file vaultpass > output.txt 2>&1
+
+echo "directory include with encrypted"
+ansible testhost -i ../../inventory -m include_vars -a 'dir=dir' -vvv --vault-password-file vaultpass >> output.txt 2>&1
+
+grep -q 'output has been hidden' output.txt
+
+# all content should be masked if any file is encrypted
+if grep -e 'i am a secret' -e 'porter.*cable' output.txt; then
+ echo "FAIL: vault masking failed"
+ exit 1
+fi
+
+echo PASS
diff --git a/test/integration/targets/include_vars-ad-hoc/vaultpass b/test/integration/targets/include_vars-ad-hoc/vaultpass
new file mode 100755
index 0000000..1f78d41
--- /dev/null
+++ b/test/integration/targets/include_vars-ad-hoc/vaultpass
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo supersecurepassword
diff --git a/test/integration/targets/no_log/action_plugins/action_sets_no_log.py b/test/integration/targets/no_log/action_plugins/action_sets_no_log.py
new file mode 100644
index 0000000..cb42616
--- /dev/null
+++ b/test/integration/targets/no_log/action_plugins/action_sets_no_log.py
@@ -0,0 +1,8 @@
+from __future__ import annotations
+
+from ansible.plugins.action import ActionBase
+
+
+class ActionModule(ActionBase):
+ def run(self, tmp=None, task_vars=None):
+ return dict(changed=False, failed=False, msg="action result should be masked", _ansible_no_log="yeppers") # ensure that a truthy non-bool works here
diff --git a/test/integration/targets/no_log/ansible_no_log_in_result.yml b/test/integration/targets/no_log/ansible_no_log_in_result.yml
new file mode 100644
index 0000000..a80a4a7
--- /dev/null
+++ b/test/integration/targets/no_log/ansible_no_log_in_result.yml
@@ -0,0 +1,13 @@
+- hosts: localhost
+ gather_facts: no
+ tasks:
+ - action_sets_no_log:
+ register: res_action
+
+ - assert:
+ that:
+ - res_action.msg == "action result should be masked"
+
+ - action_sets_no_log:
+ loop: [1, 2, 3]
+ register: res_action
diff --git a/test/integration/targets/no_log/dynamic.yml b/test/integration/targets/no_log/dynamic.yml
index 4a1123d..9523677 100644
--- a/test/integration/targets/no_log/dynamic.yml
+++ b/test/integration/targets/no_log/dynamic.yml
@@ -1,27 +1,42 @@
- name: test dynamic no log
hosts: testhost
gather_facts: no
- ignore_errors: yes
tasks:
- name: no loop, task fails, dynamic no_log
- debug:
- msg: "SHOW {{ var_does_not_exist }}"
+ raw: echo {{ var_does_not_exist }}
no_log: "{{ not (unsafe_show_logs|bool) }}"
+ ignore_errors: yes
+ register: result
+
+ - assert:
+ that:
+ - result is failed
+ - result.results is not defined
- name: loop, task succeeds, dynamic does no_log
- debug:
- msg: "SHOW {{ item }}"
+ raw: echo {{ item }}
loop:
- a
- b
- c
no_log: "{{ not (unsafe_show_logs|bool) }}"
+ register: result
+
+ - assert:
+ that:
+ - result.results | length == 3
- name: loop, task fails, dynamic no_log
- debug:
- msg: "SHOW {{ var_does_not_exist }}"
+ raw: echo {{ var_does_not_exist }}
loop:
- a
- b
- c
no_log: "{{ not (unsafe_show_logs|bool) }}"
+ ignore_errors: yes
+ register: result
+
+ - assert:
+ that:
+ - result is failed
+ - result.results is not defined # DT needs result.results | length == 3
diff --git a/test/integration/targets/no_log/no_log_config.yml b/test/integration/targets/no_log/no_log_config.yml
index 8a50880..165f4e0 100644
--- a/test/integration/targets/no_log/no_log_config.yml
+++ b/test/integration/targets/no_log/no_log_config.yml
@@ -10,4 +10,4 @@
- debug:
- debug:
- loop: '{{ range(3) }}'
+ loop: '{{ range(3) | list }}'
diff --git a/test/integration/targets/no_log/no_log_local.yml b/test/integration/targets/no_log/no_log_local.yml
index aacf7de..d2bc5ae 100644
--- a/test/integration/targets/no_log/no_log_local.yml
+++ b/test/integration/targets/no_log/no_log_local.yml
@@ -4,19 +4,22 @@
hosts: testhost
gather_facts: no
tasks:
+ - include_vars: secretvars.yml
+ no_log: true
+
- name: args should be logged in the absence of no_log
- shell: echo "LOG_ME_TASK_SUCCEEDED"
+ shell: echo "{{log_me_prefix}}TASK_SUCCEEDED"
- name: failed args should be logged in the absence of no_log
- shell: echo "LOG_ME_TASK_FAILED"
+ shell: echo "{{log_me_prefix}}TASK_FAILED"
failed_when: true
ignore_errors: true
- name: item args should be logged in the absence of no_log
shell: echo {{ item }}
- with_items: [ "LOG_ME_ITEM", "LOG_ME_SKIPPED", "LOG_ME_ITEM_FAILED" ]
- when: item != "LOG_ME_SKIPPED"
- failed_when: item == "LOG_ME_ITEM_FAILED"
+ with_items: [ "{{log_me_prefix}}ITEM", "{{log_me_prefix}}SKIPPED", "{{log_me_prefix}}ITEM_FAILED" ]
+ when: item != log_me_prefix ~ "SKIPPED"
+ failed_when: item == log_me_prefix ~ "ITEM_FAILED"
ignore_errors: true
- name: args should not be logged when task-level no_log set
@@ -61,7 +64,7 @@
no_log: true
- name: args should be logged when task-level no_log overrides play-level
- shell: echo "LOG_ME_OVERRIDE"
+ shell: echo "{{log_me_prefix}}OVERRIDE"
no_log: false
- name: Add a fake host for next play
diff --git a/test/integration/targets/no_log/no_log_suboptions.yml b/test/integration/targets/no_log/no_log_suboptions.yml
index e67ecfe..338a871 100644
--- a/test/integration/targets/no_log/no_log_suboptions.yml
+++ b/test/integration/targets/no_log/no_log_suboptions.yml
@@ -5,20 +5,20 @@
tasks:
- name: Task with suboptions
module:
- secret: GLAMOROUS
+ secret: "{{ s106 }}"
subopt_dict:
- str_sub_opt1: AFTERMATH
+ str_sub_opt1: "{{ s107 }}"
str_sub_opt2: otherstring
nested_subopt:
- n_subopt1: MANPOWER
+ n_subopt1: "{{ s101 }}"
subopt_list:
- - subopt1: UNTAPPED
+ - subopt1: "{{ s102 }}"
subopt2: thridstring
- - subopt1: CONCERNED
+ - subopt1: "{{ s103 }}"
- name: Task with suboptions as string
module:
- secret: MARLIN
- subopt_dict: str_sub_opt1=FLICK
+ secret: "{{ s104 }}"
+ subopt_dict: str_sub_opt1={{ s105 }}
diff --git a/test/integration/targets/no_log/no_log_suboptions_invalid.yml b/test/integration/targets/no_log/no_log_suboptions_invalid.yml
index 933a8a9..1092cf5 100644
--- a/test/integration/targets/no_log/no_log_suboptions_invalid.yml
+++ b/test/integration/targets/no_log/no_log_suboptions_invalid.yml
@@ -4,42 +4,45 @@
ignore_errors: yes
tasks:
+ - include_vars: secretvars.yml
+ no_log: true
+
- name: Task with suboptions and invalid parameter
module:
- secret: SUPREME
+ secret: "{{ s201 }}"
invalid: param
subopt_dict:
- str_sub_opt1: IDIOM
+ str_sub_opt1: "{{ s202 }}"
str_sub_opt2: otherstring
nested_subopt:
- n_subopt1: MOCKUP
+ n_subopt1: "{{ s203 }}"
subopt_list:
- - subopt1: EDUCATED
+ - subopt1: "{{ s204 }}"
subopt2: thridstring
- - subopt1: FOOTREST
+ - subopt1: "{{ s205 }}"
- name: Task with suboptions as string with invalid parameter
module:
- secret: FOOTREST
+ secret: "{{ s213 }}"
invalid: param
- subopt_dict: str_sub_opt1=CRAFTY
+ subopt_dict: str_sub_opt1={{ s206 }}
- name: Task with suboptions with dict instead of list
module:
- secret: FELINE
+ secret: "{{ s207 }}"
subopt_dict:
- str_sub_opt1: CRYSTAL
+ str_sub_opt1: "{{ s208 }}"
str_sub_opt2: otherstring
nested_subopt:
- n_subopt1: EXPECTANT
+ n_subopt1: "{{ s209 }}"
subopt_list:
foo: bar
- name: Task with suboptions with incorrect data type
module:
- secret: AGROUND
+ secret: "{{ s210 }}"
subopt_dict: 9068.21361
subopt_list:
- - subopt1: GOLIATH
- - subopt1: FREEFALL
+ - subopt1: "{{ s211 }}"
+ - subopt1: "{{ s212 }}"
diff --git a/test/integration/targets/no_log/runme.sh b/test/integration/targets/no_log/runme.sh
index 8bfe019..d6476ac 100755
--- a/test/integration/targets/no_log/runme.sh
+++ b/test/integration/targets/no_log/runme.sh
@@ -1,6 +1,12 @@
#!/usr/bin/env bash
-set -eux
+set -eux -o pipefail
+
+# ensure _ansible_no_log returned by actions is actually respected
+ansible-playbook ansible_no_log_in_result.yml -vvvvv > "${OUTPUT_DIR}/output.log" 2> /dev/null
+
+[ "$(grep -c "action result should be masked" "${OUTPUT_DIR}/output.log")" = "0" ]
+[ "$(grep -c "the output has been hidden" "${OUTPUT_DIR}/output.log")" = "4" ]
# This test expects 7 loggable vars and 0 non-loggable ones.
# If either mismatches it fails, run the ansible-playbook command to debug.
@@ -9,18 +15,18 @@ set -eux
# deal with corner cases with no log and loops
# no log enabled, should produce 6 censored messages
-[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=no|grep -c 'output has been hidden')" = "6" ]
+[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=no|grep -c 'output has been hidden')" = "6" ] # DT needs 7
# no log disabled, should produce 0 censored
[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=yes|grep -c 'output has been hidden')" = "0" ]
# test no log for sub options
-[ "$(ansible-playbook no_log_suboptions.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(MANPOWER|UNTAPPED|CONCERNED|MARLIN|FLICK)')" = "0" ]
+[ "$(ansible-playbook no_log_suboptions.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'SECRET')" = "0" ]
# test invalid data passed to a suboption
-[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(SUPREME|IDIOM|MOCKUP|EDUCATED|FOOTREST|CRAFTY|FELINE|CRYSTAL|EXPECTANT|AGROUND|GOLIATH|FREEFALL)')" = "0" ]
+[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'SECRET')" = "0" ]
# test variations on ANSIBLE_NO_LOG
[ "$(ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
[ "$(ANSIBLE_NO_LOG=0 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
-[ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "6" ]
+[ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "6" ] # DT needs 5
diff --git a/test/integration/targets/no_log/runme.sh.orig b/test/integration/targets/no_log/runme.sh.orig
new file mode 100755
index 0000000..8bfe019
--- /dev/null
+++ b/test/integration/targets/no_log/runme.sh.orig
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+set -eux
+
+# This test expects 7 loggable vars and 0 non-loggable ones.
+# If either mismatches it fails, run the ansible-playbook command to debug.
+[ "$(ansible-playbook no_log_local.yml -i ../../inventory -vvvvv "$@" | awk \
+'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "26/0" ]
+
+# deal with corner cases with no log and loops
+# no log enabled, should produce 6 censored messages
+[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=no|grep -c 'output has been hidden')" = "6" ]
+
+# no log disabled, should produce 0 censored
+[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=yes|grep -c 'output has been hidden')" = "0" ]
+
+# test no log for sub options
+[ "$(ansible-playbook no_log_suboptions.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(MANPOWER|UNTAPPED|CONCERNED|MARLIN|FLICK)')" = "0" ]
+
+# test invalid data passed to a suboption
+[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(SUPREME|IDIOM|MOCKUP|EDUCATED|FOOTREST|CRAFTY|FELINE|CRYSTAL|EXPECTANT|AGROUND|GOLIATH|FREEFALL)')" = "0" ]
+
+# test variations on ANSIBLE_NO_LOG
+[ "$(ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
+[ "$(ANSIBLE_NO_LOG=0 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
+[ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "6" ]
diff --git a/test/integration/targets/no_log/secretvars.yml b/test/integration/targets/no_log/secretvars.yml
new file mode 100644
index 0000000..0030d74
--- /dev/null
+++ b/test/integration/targets/no_log/secretvars.yml
@@ -0,0 +1,32 @@
+# These values are in a separate vars file and referenced dynamically to avoid spurious counts from contextual error messages
+# that show the playbook contents inline (since unencrypted playbook contents are not considered secret).
+log_me_prefix: LOG_ME_
+
+# Unique values are used for each secret below to ensure that one secret "learned" does not cause another non-secret
+# value to be considered secret simply because they share the same value. A common substring is, however, present in
+# each one to simplify searching for secret values in test output. Having a unique value for each also helps in
+# debugging when unexpected output is encountered.
+
+# secrets for no_log_suboptions.yml
+s101: SECRET101
+s102: SECRET102
+s103: SECRET103
+s104: SECRET104
+s105: SECRET105
+s106: SECRET106
+s107: SECRET107
+
+# secrets for no_log_suboptions_invalid.yml
+s201: SECRET201
+s202: SECRET202
+s203: SECRET203
+s204: SECRET204
+s205: SECRET205
+s206: SECRET206
+s207: SECRET207
+s208: SECRET208
+s209: SECRET209
+s210: SECRET210
+s211: SECRET211
+s212: SECRET212
+s213: SECRET213

146
CVE-2024-9902.patch Normal file
View File

@ -0,0 +1,146 @@
From: Brian Coca <bcoca@users.noreply.github.com>
Date: Mon, 28 Oct 2024 12:47:08 -0400
Subject: CVE-2024-9902 user module avoid chmoding symlink'd home file
(#83956) (#84081)
also added tests
origin: https://github.com/ansible/ansible/commit/3b5a4319985e1eabe7fc0410bf8308b671f4f586
bug: https://github.com/ansible/ansible/pull/84081
bug-debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1086883
---
changelogs/fragments/user_action_fix.yml | 2 +
lib/ansible/modules/system/user.py | 4 +-
.../targets/user/files/skel/.ssh/known_hosts | 1 +
.../user/tasks/test_create_user_home.yml | 86 +++++++++++++++++++
4 files changed, 92 insertions(+), 1 deletion(-)
create mode 100644 changelogs/fragments/user_action_fix.yml
create mode 100644 test/integration/targets/user/files/skel/.ssh/known_hosts
diff --git a/changelogs/fragments/user_action_fix.yml b/changelogs/fragments/user_action_fix.yml
new file mode 100644
index 00000000..64ee997d
--- /dev/null
+++ b/changelogs/fragments/user_action_fix.yml
@@ -0,0 +1,2 @@
+bugfixes:
+ - user module now avoids changing ownership of files symlinked in provided home dir skeleton
diff --git a/lib/ansible/modules/system/user.py b/lib/ansible/modules/system/user.py
index fd56fc68..9d47425f 100644
--- a/lib/ansible/modules/system/user.py
+++ b/lib/ansible/modules/system/user.py
@@ -1154,7 +1154,9 @@ class User(object):
for d in dirs:
os.chown(os.path.join(root, d), uid, gid)
for f in files:
- os.chown(os.path.join(root, f), uid, gid)
+ full_path = os.path.join(root, f)
+ if not os.path.islink(full_path):
+ os.chown(full_path, uid, gid)
except OSError as e:
self.module.exit_json(failed=True, msg="%s" % to_native(e))
diff --git a/test/integration/targets/user/files/skel/.ssh/known_hosts b/test/integration/targets/user/files/skel/.ssh/known_hosts
new file mode 100644
index 00000000..f5b72a21
--- /dev/null
+++ b/test/integration/targets/user/files/skel/.ssh/known_hosts
@@ -0,0 +1 @@
+test file, not real ssh hosts file
diff --git a/test/integration/targets/user/tasks/test_create_user_home.yml b/test/integration/targets/user/tasks/test_create_user_home.yml
index 1b529f76..3bc1023b 100644
--- a/test/integration/targets/user/tasks/test_create_user_home.yml
+++ b/test/integration/targets/user/tasks/test_create_user_home.yml
@@ -134,3 +134,89 @@
name: randomuser
state: absent
remove: yes
+
+- name: Create user home directory with /dev/null as skeleton, https://github.com/ansible/ansible/issues/75063
+ # create_homedir is mostly used by linux, rest of OSs take care of it themselves via -k option (which fails this task)
+ # OS X actuall breaks since it does not implement getpwnam()
+ when: ansible_system == 'Linux'
+ block:
+ - name: "Create user home directory with /dev/null as skeleton"
+ user:
+ name: withskeleton
+ state: present
+ skeleton: "/dev/null"
+ createhome: yes
+ register: create_user_with_skeleton_dev_null
+ always:
+ - name: "Remove test user"
+ user:
+ name: withskeleton
+ state: absent
+ remove: yes
+
+- name: Create user home directory with skel that contains symlinks
+ tags: symlink_home
+ when: ansible_system == 'Linux'
+ become: True
+ vars:
+ flag: '{{tempdir.path}}/root_flag.conf'
+ block:
+ - name: make tempdir for skel
+ tempfile: state=directory
+ register: tempdir
+
+ - name: create flag file
+ file: path={{flag}} owner=root state=touch
+
+ - name: copy skell to target
+ copy:
+ dest: '{{tempdir.path}}/skel'
+ src: files/skel
+ register: skel
+
+ - name: create the bad symlink
+ file:
+ src: '{{flag}}'
+ dest: '{{tempdir.path}}/skel/should_not_change_own'
+ state: link
+
+ - name: "Create user home directory with skeleton"
+ user:
+ name: withskeleton
+ state: present
+ skeleton: "{{tempdir.path}}/skel"
+ createhome: yes
+ home: /home/missing/withskeleton
+ register: create_user_with_skeleton_symlink
+
+ - name: Check flag
+ stat: path={{flag}}
+ register: test_flag
+
+ - name: ensure we didn't change owner for flag
+ assert:
+ that:
+ - test_flag.stat.uid != create_user_with_skeleton_symlink.uid
+
+ always:
+ - name: "Remove test user"
+ user:
+ name: withskeleton
+ state: absent
+ remove: yes
+
+ - name: get files to delete
+ find: path="{{tempdir.path}}"
+ register: remove
+ when:
+ - tempdir is defined
+ - tempdir is success
+
+ - name: "Remove temp files"
+ file:
+ path: '{{item}}'
+ state: absent
+ loop: "{{remove.files|default([])}}"
+ when:
+ - remove is success
+
--
2.47.0

View File

@ -2,7 +2,7 @@
Name: ansible Name: ansible
Summary: SSH-based configuration management, deployment, and task execution system Summary: SSH-based configuration management, deployment, and task execution system
Version: 2.9.27 Version: 2.9.27
Release: 5 Release: 6
License: GPLv3+ License: GPLv3+
Source0: https://releases.ansible.com/ansible/%{name}-%{version}.tar.gz Source0: https://releases.ansible.com/ansible/%{name}-%{version}.tar.gz
@ -19,6 +19,8 @@ Patch4: ansible-2.9.23-sphinx4.patch
Patch5: hostname-module-support-openEuler.patch Patch5: hostname-module-support-openEuler.patch
Patch6: Fix-build-error-for-sphinx-7.0.patch Patch6: Fix-build-error-for-sphinx-7.0.patch
Patch7: CVE-2024-0690.patch Patch7: CVE-2024-0690.patch
Patch8: CVE-2024-8775.patch
Patch9: CVE-2024-9902.patch
Provides: ansible-python3 = %{version}-%{release} Provides: ansible-python3 = %{version}-%{release}
Obsoletes: ansible-python3 < %{version}-%{release} Obsoletes: ansible-python3 < %{version}-%{release}
@ -216,6 +218,9 @@ make PYTHON=/usr/bin/python3 tests-py3
%{python3_sitelib}/ansible_test %{python3_sitelib}/ansible_test
%changelog %changelog
* Mon Dec 02 2024 yaoxin <yao_xin001@hoperun.com> - 2.9.27-6
- Fix CVE-2024-8775 and CVE-2024-9902
* Mon Feb 05 2024 wangkai <13474090681@163.com> - 2.9.27-5 * Mon Feb 05 2024 wangkai <13474090681@163.com> - 2.9.27-5
- Fix CVE-2024-0690 - Fix CVE-2024-0690