Integrate reviewed removal fixture and release metadata controls

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-13 00:58:31 +01:00
4 changed files with 104 additions and 6 deletions
+71 -3
View File
@@ -11,6 +11,7 @@ from pathlib import Path
import shlex
import stat
import subprocess
import sys
import tempfile
import unittest
@@ -113,6 +114,32 @@ save(); sys.exit(0)
'''
LINK_MODEL = r'''
import os, pathlib, sys
args = sys.argv[1:]
if len(args) == 4 and args[:2] == ['-T', '--']:
no_target_directory, source, target = True, args[2], args[3]
elif len(args) == 3 and args[0] == '--':
no_target_directory, source, target = False, args[1], args[2]
else:
sys.exit(98)
root = pathlib.Path(os.environ['FIXTURE']).resolve()
for name in (source, target):
path = pathlib.Path(name)
if not path.is_absolute() or '..' in path.parts or not path.parent.resolve().is_relative_to(root):
sys.exit(97)
# Exercise the real exclusive link operation, not a simulated successful exit.
# DSM's ln -T must not follow a raced target directory. The old flagless form
# deliberately retains directory-following semantics for adverse source checks.
if not no_target_directory and os.path.isdir(target):
target = os.path.join(target, os.path.basename(source))
try:
os.link(source, target, follow_symlinks=False)
except OSError:
sys.exit(1)
'''
class AgentUpstartTeardown(unittest.TestCase):
source = SOURCE.read_text()
@@ -123,6 +150,12 @@ class AgentUpstartTeardown(unittest.TestCase):
tools = root / "tools"; tools.mkdir()
for name in ("ps", "initctl"):
p = tools / name; p.write_text(MODEL); p.chmod(0o700)
link = tools / "fixture-link.py"; link.write_text(LINK_MODEL)
# Native macOS ln lacks -T. No host ln implementation may determine
# whether these closed DSM semantics or restoration controls pass.
external_link = tools / "ln"
external_link.write_text("#!/bin/sh\nprintf 'unexpected external ln\\n' >&2\nexit 96\n")
external_link.chmod(0o700)
if fault == "missing-initctl": (tools / "initctl").unlink()
init = root / "init"; init.mkdir()
config = init / "pulse-agent.conf"
@@ -160,7 +193,7 @@ class AgentUpstartTeardown(unittest.TestCase):
values = dict(FIXTURE=root, EVENTS=root / "events", STATE_DIR=root / "state",
PRIVILEGED_HELPER_STATE_DIR=root / "helper", SAFE_PROFILE_STATE_DIR=root / "profile",
PRIVILEGE_SUDOERS_FILE=root / "absent-sudoers", PRIVILEGE_HELPER_DIR=root / "absent-helper",
REPLY=reply, FAULT=fault)
REPLY=reply, FAULT=fault, FIXTURE_PYTHON=sys.executable, FIXTURE_LINK=link)
setup = "\n".join(k + "=" + shlex.quote(str(v)) for k, v in values.items()) + "\n"
shell = r'''
set -euo pipefail
@@ -213,8 +246,9 @@ ln() {
race_restore_config "$target"
fi
printf 'file:link\n' >> "$EVENTS"
# Preserve the actual external command exit for the production handler.
if builtin command ln "$@"; then return 0; else return $?; fi
# A portable private link model preserves the actual syscall exit, including
# raced files/directories. Never fall back to success or weaken production -T.
if "$FIXTURE_PYTHON" "$FIXTURE_LINK" "$@"; then return 0; else return $?; fi
}
mv() {
local arg
@@ -273,6 +307,40 @@ remove_safe_profile_state_dir() { builtin command rm -r -- "$SAFE_PROFILE_STATE_
self.assertTrue(added <= {"init/pulse-agent.override"}, added)
if added: self.assertEqual(after["init/pulse-agent.override"][2], b"manual\n")
def test_link_fixture_preserves_exclusive_no_target_directory_semantics(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source = root / "source"; source.write_bytes(b"original recovery\n")
target = root / "target"
env = dict(os.environ, FIXTURE=str(root))
def link(*args):
return subprocess.run([sys.executable, "-c", LINK_MODEL, *map(str, args)],
env=env, capture_output=True, timeout=5).returncode
self.assertEqual(link('-T', '--', source, target), 0)
self.assertEqual(target.stat().st_ino, source.stat().st_ino)
target.unlink(); target.write_bytes(b"concurrent recovery\n")
self.assertEqual(link('-T', '--', source, target), 1)
self.assertEqual(target.read_bytes(), b"concurrent recovery\n")
target.unlink(); target.mkdir()
self.assertEqual(link('-T', '--', source, target), 1)
self.assertEqual(list(target.iterdir()), [])
self.assertEqual(link('--', source, target), 0)
self.assertEqual((target / source.name).stat().st_ino, source.stat().st_ino)
self.assertEqual(link('-f', '--', source, target), 98)
self.assertEqual(link('-T', '--', source, root / '..' / 'foreign'), 97)
def test_link_fixture_exposes_the_old_directory_restore_race(self):
# The portable model must not launder the unsafe old argv into -T.
self.source = self.source.replace('ln -T --', 'ln --')
r, events, before, after, _ = self.exercise(fault="concurrent-restore-directory")
self.assertNotEqual(r.returncode, 0)
self.assertIn("remote:remove", events)
self.assertNotIn("state:remove", events)
nested = [n for n in after if n.startswith('init/pulse-agent.conf/')]
self.assertEqual(len(nested), 1)
self.assertEqual(after[nested[0]], before['init/pulse-agent.conf'])
self.assertFalse(any(n.startswith('init/pulse-agent.conf.removal.') for n in after))
def test_refused_manager_precedes_remote_and_state_erasure(self):
for fault in ("refused-stop", "refused-reload", "ignored-stop", "ignored-manual"):
with self.subTest(fault=fault):
@@ -1320,6 +1320,12 @@ func TestCurrentPrereleasePacketTracksInstallMetadata(t *testing.T) {
)
assertFileDoesNotContain(t, releaseNotesPath, "## Fixes")
comparisonSummary := "This changelog describes the changes since `v" + comparisonVersion + "`"
if version == "6.6.0-beta.2" {
// This packet's recorded list is cumulative from stable, not beta.1.
// Keep its introduction bound to that actual list and rollback base.
comparisonSummary = "This cumulative changelog describes the changes included in `v" + version + "` since stable `v" + previous + "`"
assertFileContainsAllNormalized(t, changelogPath, "## Changes since v"+previous)
}
if version == "6.4.0-rc.10" {
comparisonSummary = "The `v6.4.0-rc.9` release staged an immutable draft, tag, and exact-version artifacts but did not activate publicly."
}
+3 -1
View File
@@ -8478,6 +8478,8 @@ func TestInstallSHUpstartTeardownRequiresObservedQuiescence(t *testing.T) {
t.Fatalf("DSM Upstart retirement and production EXIT cleanup controls: %v\n%s", err, output)
}
for _, name := range []string{
"test_link_fixture_preserves_exclusive_no_target_directory_semantics",
"test_link_fixture_exposes_the_old_directory_restore_race",
"test_refused_manager_precedes_remote_and_state_erasure",
"test_failed_empty_partial_diagnostic_status_cannot_authorise_removal",
"test_effective_disabled_config_is_required_after_override_reload",
@@ -8498,7 +8500,7 @@ func TestInstallSHUpstartTeardownRequiresObservedQuiescence(t *testing.T) {
t.Fatalf("DSM Upstart control %s did not pass:\n%s", name, output)
}
}
if !strings.Contains(string(output), "\nRan 15 tests in ") || !strings.Contains(string(output), "\nOK\n") {
if !strings.Contains(string(output), "\nRan 17 tests in ") || !strings.Contains(string(output), "\nOK\n") {
t.Fatalf("incomplete DSM Upstart verdict:\n%s", output)
}
t.Logf("%s", output)
+24 -2
View File
@@ -49,12 +49,34 @@ class AgentUninstallDocsTest(unittest.TestCase):
for operation in ('remove_agent_state_dir "$STATE_DIR"',
"teardown_action_runner_service", "teardown_privileged_helper_service",
"remove_privileged_helper_state_dir", "remove_safe_profile_state_dir",
'rm -f /var/log/pulse-agent.log', 'GO_SCRIPT="/boot/config/go"',
"remove_qnap_autorun_block"):
'rm -f /var/log/pulse-agent.log'):
self.assertIn(operation, self.removal)
self.assert_nas_boot_retirement_before_erasure(self.removal)
self.assertIn('Remove-Item $StateDir -Recurse -Force',
(ROOT / "scripts/install.ps1").read_text())
def assert_nas_boot_retirement_before_erasure(self, removal):
# Removal no longer shares installation's best-effort boot-file edits.
# Bind the existing boot-entry warning to the checked NAS lifecycle.
quiesce = 'quiesce_nas_agent_for_removal "$uninstall_nas_platform"'
retire = 'teardown_nas_agent_boot_owner "$uninstall_nas_platform"'
for call in (quiesce, retire):
self.assertIn(call + ' ||\n fail ', removal)
self.assertLess(removal.index(quiesce), removal.index('uninstall_collector_registration'))
self.assertLess(removal.index(retire), removal.index('remove_agent_state_dir "$STATE_DIR"'))
for old_edit in ('GO_SCRIPT="/boot/config/go"', 'remove_qnap_autorun_block'):
self.assertNotIn(old_edit, removal)
def test_nas_boot_guidance_rejects_missing_late_or_ignored_retirement(self):
call = 'teardown_nas_agent_boot_owner "$uninstall_nas_platform"'
late = self.removal.replace(call, ':').replace('remove_agent_state_dir "$STATE_DIR"',
'remove_agent_state_dir "$STATE_DIR"; ' + call + ' ||\n fail "late" 1')
for changed in (self.removal.replace(call, ':'), late,
self.removal.replace(call + ' ||', call + ' || true #')):
with self.subTest(mutation=changed):
with self.assertRaises(AssertionError):
self.assert_nas_boot_retirement_before_erasure(changed)
def test_saved_state_trust_and_explicit_path_authority_are_not_bypassed(self):
for fact in ("same trusted Pulse instance", "on the affected agent host",
"top-level `install.sh`", "Uninstallation needs no new token",