From 1f1d3ded416f84c92eee34e20f99152fcd2c8a2c Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Wed, 16 Sep 2026 10:34:21 -0400 Subject: [PATCH] FIX: root-own the Salt default tree so a SOC file-write cannot reach root code /opt/so/saltstack/default holds the source for every root-executed script -- /usr/sbin, the reactors, _runners/_modules/_beacons, the master engines and salt-relay.sh -- plus every state the root master renders. SOC mounts /opt/so/saltstack rw as uid 939, so root-owning /usr/sbin alone was not enough: the next highstate would copy attacker-controlled bytes out of the tree into the root-owned destination and run them. SOC never writes under default/, it only reads it. Every SOC write targets local/, which stays socore-owned, as does /opt/so/state. No mode is enforced on default/ -- SOC reads that tree, and 750/640 would break its config load. Also stops copy_new_files(), so-saltstack-update and setup from chowning the tree back to socore, and replaces preserve: True in soup_scripts.sls, which carried uid/gid in from the /tmp staging tree and would have undone the ownership before the first post-soup highstate. --- salt/common/soup_scripts.sls | 56 +++++++++++++++------ salt/common/tools/sbin/so-common | 3 +- salt/manager/init.sls | 10 ++-- salt/manager/tools/sbin/so-saltstack-update | 4 +- salt/soc/dyanno/hypervisor/init.sls | 4 +- setup/so-functions | 2 + 6 files changed, 57 insertions(+), 22 deletions(-) diff --git a/salt/common/soup_scripts.sls b/salt/common/soup_scripts.sls index 6b2f1551d..50be19bb4 100644 --- a/salt/common/soup_scripts.sls +++ b/salt/common/soup_scripts.sls @@ -18,47 +18,61 @@ copy_so-common_common_tools_sbin: - name: /opt/so/saltstack/default/salt/common/tools/sbin/so-common - source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_so-image-common_common_tools_sbin: file.copy: - name: /opt/so/saltstack/default/salt/common/tools/sbin/so-image-common - source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_soup_manager_tools_sbin: file.copy: - name: /opt/so/saltstack/default/salt/manager/tools/sbin/soup - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_so-firewall_manager_tools_sbin: file.copy: - name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-firewall - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_so-yaml_manager_tools_sbin: file.copy: - name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-yaml.py - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_so-repo-sync_manager_tools_sbin: file.copy: - name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-repo-sync - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync - - preserve: True + - user: root + - group: root + - mode: 755 copy_bootstrap-salt_manager_tools_sbin: file.copy: - name: /opt/so/saltstack/default/salt/salt/scripts/bootstrap-salt.sh - source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh - - preserve: True + - user: root + - group: root + - mode: 644 # This section is used to put the new script in place so that it can be called during soup. # It is faster than calling the states that normally manage them to put them in place. @@ -67,46 +81,60 @@ copy_so-common_sbin: - name: /usr/sbin/so-common - source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_so-image-common_sbin: file.copy: - name: /usr/sbin/so-image-common - source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_soup_sbin: file.copy: - name: /usr/sbin/soup - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_so-firewall_sbin: file.copy: - name: /usr/sbin/so-firewall - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_so-yaml_sbin: file.copy: - name: /usr/sbin/so-yaml.py - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_so-repo-sync_sbin: file.copy: - name: /usr/sbin/so-repo-sync - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync - force: True - - preserve: True + - user: root + - group: root + - mode: 755 copy_bootstrap-salt_sbin: file.copy: - name: /usr/sbin/bootstrap-salt.sh - source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh - force: True - - preserve: True + - user: root + - group: root + - mode: 755 diff --git a/salt/common/tools/sbin/so-common b/salt/common/tools/sbin/so-common index fbcd37bb9..c8ebae084 100755 --- a/salt/common/tools/sbin/so-common +++ b/salt/common/tools/sbin/so-common @@ -240,7 +240,8 @@ copy_new_files() { cd $UPDATE_DIR rsync -a salt $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}" rsync -a pillar $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}" - chown -R socore:socore $DEFAULT_SALT_DIR/ + # Root-executed code; SOC only needs to read it. Local dirs stay socore-owned. + chown -R root:root $DEFAULT_SALT_DIR/ cd /tmp } diff --git a/salt/manager/init.sls b/salt/manager/init.sls index 85df680d4..87669fad4 100644 --- a/salt/manager/init.sls +++ b/salt/manager/init.sls @@ -190,11 +190,15 @@ so_fleetagent_monitor: - month: '*' - dayweek: '*' -socore_own_saltstack_default: +# This tree is the source of every root-executed script (/usr/sbin, reactors, _runners, +# engines, salt-relay.sh). SOC mounts /opt/so/saltstack rw as uid 939 but only writes +# under local/. Do not add dir_mode/file_mode here -- SOC reads default/ and 750/640 +# would break its config load. +root_own_saltstack_default: file.directory: - name: /opt/so/saltstack/default - - user: socore - - group: socore + - user: root + - group: root - recurse: - user - group diff --git a/salt/manager/tools/sbin/so-saltstack-update b/salt/manager/tools/sbin/so-saltstack-update index c8723beb0..4ff4b0519 100755 --- a/salt/manager/tools/sbin/so-saltstack-update +++ b/salt/manager/tools/sbin/so-saltstack-update @@ -124,8 +124,8 @@ copy_new_files() { rsync -a salt $default_salt_dir/ rsync -a pillar $default_salt_dir/ - chown -R socore:socore $default_salt_dir/salt - chown -R socore:socore $default_salt_dir/pillar + chown -R root:root $default_salt_dir/salt + chown -R root:root $default_salt_dir/pillar chmod 755 $default_salt_dir/pillar/firewall/addfirewall.sh rm -rf /tmp/sogh diff --git a/salt/soc/dyanno/hypervisor/init.sls b/salt/soc/dyanno/hypervisor/init.sls index 1f17552e7..41b7a2ebf 100644 --- a/salt/soc/dyanno/hypervisor/init.sls +++ b/salt/soc/dyanno/hypervisor/init.sls @@ -18,8 +18,8 @@ hypervisor_annotation: - name: /opt/so/saltstack/default/salt/hypervisor/soc_hypervisor.yaml - source: salt://soc/dyanno/hypervisor/soc_hypervisor.yaml.jinja - template: jinja - - user: socore - - group: socore + - user: root + - group: root - defaults: HYPERVISORS: {{ HYPERVISORS }} baseDomainStatus: {{ salt['pillar.get']('baseDomain:status', 'Initialized') }} diff --git a/setup/so-functions b/setup/so-functions index 1269b4e9d..64e809be5 100755 --- a/setup/so-functions +++ b/setup/so-functions @@ -2105,6 +2105,8 @@ setup_salt_master_dirs() { info "Chown the salt dirs on the manager for socore" logCmd "chown -R socore:socore /opt/so" + # The default tree is root-executed code; SOC reads it but never writes it. + logCmd "chown -R root:root $default_salt_dir" } set_progress_str() {