Retain NAS recovery when a live Unraid boot shell owns pending commands

A go shell can hold later launches in memory after its boot file changes. Treat that live mixed-purpose owner as unknown rather than signalling it or inferring quiescence from the edited file. Require the actual-caller refusal control alongside the existing NAS suite.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-12 19:51:00 +01:00
parent 450d521e60
commit 5dad231852
5 changed files with 16 additions and 7 deletions
@@ -41,8 +41,9 @@ Complete bounded kernel evidence returns only numeric IDs and roles. Only exact
selected watchdog/collector processes receive checked TERM, watchdog first,
with independent absence observations. Editor sessions, backup wrappers and
co-installed names are not targets. Relative/foreign same-name processes or
unattributable QNAP deferred autorun shells refuse instead of signalling another
boot owner. Failed/diagnostic/duplicate/empty reads and ignored/refused signals
unattributable QNAP deferred autorun or live Unraid go shells refuse instead
of signalling another boot owner. A live go shell may already hold later boot
commands in memory; editing its file cannot establish quiescence. Failed/diagnostic/duplicate/empty reads and ignored/refused signals
retain identity, credentials, trust and recoverable files. QNAP mount and
unmount each have checked bounded results, including cleanup after partial mount.
@@ -54,7 +55,7 @@ Unraid directories. Successful/absent/repeated removal still requires complete
fresh observations. No automatic restart, credential churn or transport-policy
change follows a partial operation.
`TestInstallSHNASBootOwnersQuiesceBeforeRecoveryErasure` requires all seventeen
`TestInstallSHNASBootOwnersQuiesceBeforeRecoveryErasure` requires all eighteen
actual-function/full-uninstall caller controls in `agent_nas_teardown.py`. The
boot filesystem transformations are real private user operations; flash/kernel
faults are closed models. Two generated watchdog/child controls use real private
@@ -42,8 +42,9 @@ Complete bounded kernel evidence returns only numeric IDs and roles. Only exact
selected watchdog/collector processes receive checked TERM, watchdog first,
with independent absence observations. Editor sessions, backup wrappers and
co-installed names are not targets. Relative/foreign same-name processes or
unattributable QNAP deferred autorun shells refuse instead of signalling another
boot owner. Failed/diagnostic/duplicate/empty reads and ignored/refused signals
unattributable QNAP deferred autorun or live Unraid go shells refuse instead
of signalling another boot owner. A live go shell may already hold later boot
commands in memory; editing its file cannot establish quiescence. Failed/diagnostic/duplicate/empty reads and ignored/refused signals
retain identity, credentials, trust and recoverable files. QNAP mount and
unmount each have checked bounded results, including cleanup after partial mount.
@@ -55,7 +56,7 @@ Unraid directories. Successful/absent/repeated removal still requires complete
fresh observations. No automatic restart, credential churn or transport-policy
change follows a partial operation.
`TestInstallSHNASBootOwnersQuiesceBeforeRecoveryErasure` requires all seventeen
`TestInstallSHNASBootOwnersQuiesceBeforeRecoveryErasure` requires all eighteen
actual-function/full-uninstall caller controls in `agent_nas_teardown.py`. The
boot filesystem transformations are real private user operations; flash/kernel
faults are closed models. Two generated watchdog/child controls use real private
+1
View File
@@ -4068,6 +4068,7 @@ read_nas_agent_removal_processes() {
if [[ "$args" == "$wrapper" || "$args" == "$wrapper "* ]]; then role=watchdog; fi
if [[ "$role" != watchdog && "$args" =~ start-pulse-agent\.sh([[:space:]]|$) ]]; then role=unknown; fi
if [[ "$platform" == QNAP && "$args" == *'/nasconfig_tmp/autorun.sh'* ]]; then role=unknown; fi
if [[ "$platform" == Unraid && "$args" == *'/boot/config/go'* ]]; then role=unknown; fi
;;
esac
rows+=("$pid $parent $role"); count=$((count + 1))
@@ -74,6 +74,7 @@ if role == 'ps':
if fault == 'foreign-process': print('102 1 pulse-agent /other/instance/pulse-agent --token-file /synthetic/private-only')
if fault == 'relative-wrapper': print('103 1 sh sh start-pulse-agent.sh')
if fault == 'deferred-bootstrap': print('104 1 sh /bin/sh /tmp/nasconfig_tmp/autorun.sh')
if fault == 'live-go': print('105 1 bash /bin/bash ' + str(root / 'go'))
print('200 1 vi vi ' + wrapper) # Editor is not a watchdog or signal target.
print('201 1 sh /bin/sh ' + wrapper + '.bak')
print('202 1 pulse-agent-prod /other/pulse-agent-prod')
@@ -366,6 +367,10 @@ remove_safe_profile_state_dir() { rm -rf -- "$SAFE_PROFILE_STATE_DIR"; }
self.assertNotIn('/synthetic/private-only', r[0].stdout); self.assertIn('200 1 other\n', r[0].stdout)
r = self.exercise('QNAP', 'timeout-process'); self.retained(r); self.assertTrue(r[9])
def test_live_unraid_boot_shell_is_unknown_not_a_signal_target(self):
r = self.exercise('Unraid', 'live-go'); events = self.retained(r)
self.assertEqual(r[6], r[7]); self.assertFalse(any(e.startswith('signal:') for e in events))
def test_recovered_ordinary_linux_state_is_not_qnap_platform_authority(self):
r = self.exercise('QNAP', appliance=False)
self.assertEqual(r[0].returncode, 0, r[0].stderr)
+2 -1
View File
@@ -8568,6 +8568,7 @@ func TestInstallSHNASBootOwnersQuiesceBeforeRecoveryErasure(t *testing.T) {
"test_absent_missing_and_repeated_removal_require_fresh_quiescence",
"test_process_observation_returns_roles_not_arguments_and_stops_timeout_child",
"test_recovered_ordinary_linux_state_is_not_qnap_platform_authority",
"test_live_unraid_boot_shell_is_unknown_not_a_signal_target",
"test_actual_generated_watchdogs_and_children_become_quiescent",
"test_caller_has_no_blanket_nas_or_legacy_tree_deletion",
} {
@@ -8575,7 +8576,7 @@ func TestInstallSHNASBootOwnersQuiesceBeforeRecoveryErasure(t *testing.T) {
t.Fatalf("NAS removal control %s did not pass:\n%s", name, output)
}
}
if !strings.Contains(string(output), "\nRan 17 tests in ") || !strings.Contains(string(output), "\nOK\n") {
if !strings.Contains(string(output), "\nRan 18 tests in ") || !strings.Contains(string(output), "\nOK\n") {
t.Fatalf("incomplete NAS removal verdict:\n%s", output)
}
t.Logf("%s", output)