Fix portable root ownership for installer lifecycle state

Issue #1890 reports macOS agent updates stopping because the root group does not exist. Use numeric superuser ownership in the two shared lifecycle writes without relaxing failure handling or the least-privilege group boundary. Add a regression fixture that rejects named root ownership and checks that chown failures still prevent replacement.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-04 22:31:41 +01:00
parent 106e115e44
commit 19c2b6a925
4 changed files with 62 additions and 2 deletions
@@ -15,6 +15,21 @@
## Purpose
### Portable installer lifecycle ownership
The shared shell installer lifecycle directory (outside the least-privilege
profile) and atomic lifecycle-file staging use numeric UID/GID `0:0` when
running as root. They must not depend on a group named `root`, which is absent
on macOS. The least-privilege directory retains its dedicated collector group
and 0750 mode; ordinary root lifecycle directories retain 0700 mode.
Ownership failure aborts directory preparation or file installation before
replacement of the saved installer. The runtime shell fixture
`TestInstallSHLifecyclePortableRootOwnership` in
`scripts/installtests/install_sh_test.go` rejects named ownership and proves
that a failed chown leaves the previously saved installer intact. This fixture
is not native macOS upgrade qualification.
`models.PBSBackup` may carry monitoring-owned write-activity evidence for an
incomplete PBS snapshot. `inProgress` means the snapshot has no completed
manifest; `writeActivityObserved` and `writeActive` distinguish a successfully
@@ -15,6 +15,21 @@
## Purpose
### Portable installer lifecycle ownership
The shared shell installer lifecycle directory (outside the least-privilege
profile) and atomic lifecycle-file staging use numeric UID/GID `0:0` when
running as root. They must not depend on a group named `root`, which is absent
on macOS. The least-privilege directory retains its dedicated collector group
and 0750 mode; ordinary root lifecycle directories retain 0700 mode.
Ownership failure aborts directory preparation or file installation before
replacement of the saved installer. The runtime shell fixture
`TestInstallSHLifecyclePortableRootOwnership` in
`scripts/installtests/install_sh_test.go` rejects named ownership and proves
that a failed chown leaves the previously saved installer intact. This fixture
is not native macOS upgrade qualification.
Own server installation, deployment bootstrap behavior, provider-hosted MSP
deployment artifacts, update planning, and server-side update execution
surfaces.
+3 -2
View File
@@ -3099,7 +3099,8 @@ prepare_installer_lifecycle_dir() {
chown "root:${LEAST_PRIVILEGE_USER}" "$lifecycle_dir" || return 1
chmod 0750 "$lifecycle_dir" || return 1
else
chown root:root "$lifecycle_dir" || return 1
# Numeric IDs also work on macOS, whose group 0 is wheel, not root.
chown 0:0 "$lifecycle_dir" || return 1
chmod 0700 "$lifecycle_dir" || return 1
fi
else
@@ -3123,7 +3124,7 @@ install_lifecycle_file_atomically() {
cp "$source_path" "$target_tmp" || return 1
chmod "$target_mode" "$target_tmp" || return 1
if [[ "$(id -u)" == "0" ]]; then
chown root:root "$target_tmp" || return 1
chown 0:0 "$target_tmp" || return 1
fi
sync_lifecycle_path "$target_tmp" || return 1
mv -f "$target_tmp" "$target_path" || return 1
+29
View File
@@ -7885,3 +7885,32 @@ func TestInstallSHTypedPrivilegedHelperProtectsCredentialsAfterStateChown(t *tes
}
}
}
// macOS has no root group. Exercise both shared lifecycle writes with a
// chown stub that accepts only portable numeric superuser ownership.
func TestInstallSHLifecyclePortableRootOwnership(t *testing.T) {
script := `set -euo pipefail
LEAST_PRIVILEGE=false
TMP_FILES=()
id() { echo 0; }
chown() { [[ "$1" == "0:0" ]]; }
sync_lifecycle_path() { :; }
` + extractInstallShellFunction(t, "prepare_installer_lifecycle_dir") + "\n" +
extractInstallShellFunction(t, "install_lifecycle_file_atomically") + `
prepare_installer_lifecycle_dir "$PWD/state"
printf 'saved installer\n' > "$PWD/source"
install_lifecycle_file_atomically "$PWD/source" "$PWD/state/install.sh" 700
cmp "$PWD/source" "$PWD/state/install.sh"
# A failed ownership operation must still prevent replacement.
chown() { return 1; }
if prepare_installer_lifecycle_dir "$PWD/rejected"; then exit 10; fi
printf 'replacement\n' > "$PWD/source"
if install_lifecycle_file_atomically "$PWD/source" "$PWD/state/install.sh" 700; then exit 11; fi
grep -qx 'saved installer' "$PWD/state/install.sh"
`
cmd := exec.Command("bash", "-c", script)
cmd.Dir = t.TempDir()
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("lifecycle ownership: %v\n%s", err, out)
}
}