Integrate reviewed executable update acceptance

Retain exact commit 18df4c1b46. Executable identity and bounded successful version probes replace metadata-only acceptance, retaining rollback and prior-active restoration. Source-uy8hwdan contains passing 24 focused methods and 191 subcases with vet; enclosing exit 1 from the incorrect control count and the independent broad installer failure remain adverse evidence, not cleared.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-07 21:58:42 +01:00
4 changed files with 147 additions and 15 deletions
@@ -53,6 +53,26 @@ editions cause no release read or installer action, while a recognised community
binary still reaches both. These ordinary-user shell fixtures do not establish
installed systemd, paid customer or native update acceptance.
### Unattended update acceptance requires executable version evidence
After the signed installer returns success, the unattended updater requires the
selected primary (or, only when absent, legacy) executable to report a recognised
community version on its first line and exit successfully within five seconds,
with a one-second KILL backstop. Optional build details on later lines remain
valid. An optional `v` prefix is normalised for comparison with the exact target.
VERSION sidecars, failed or empty probes, other editions/brands and versions
embedded in later lines cannot establish successful replacement. Unknown version
identity or a mismatch enters the existing verified rollback transaction, never
success. The same bounded read is used before release discovery.
`TestAutoUpdateBackupTransaction` executes real local signed installers that
return success but leave each adverse executable beside a target VERSION file.
It checks failure, restored original bytes/modes/metadata/configuration and
only-prior-active service restoration, with valid build-detail and unprefixed
community controls. `TestAutoUpdateVersionProbeHasHardDeadline` exercises a real
TERM-resistant executable. These are source fixtures, not native systemd or
installed customer recovery. Consent, signatures and rollback gates are unchanged.
### Unattended update consent comes from complete configuration
When `system.json` exists, the unattended updater requires one complete JSON
@@ -297,3 +297,87 @@ systemctl() { return 0; }
})
}
}
// VERSION is deliberately plausible in every fixture. Neither it nor a legacy
// executable can rescue a present primary whose own version evidence is bad.
func TestAutoUpdateVersionNeedsSuccessfulExecutable(t *testing.T) {
for _, tc := range []struct {
name, body, legacy, want string
nonExecutable, directory bool
}{
{name: "community", body: "echo 'Pulse v6.5.0'", want: "v6.5.0"},
{name: "unprefixed", body: "echo 'Pulse 6.5.0'", want: "v6.5.0"},
{name: "build_details", body: "printf 'Pulse v6.5.0\\nBuilt: yesterday\\n'", want: "v6.5.0"},
{name: "preview", body: "echo 'Pulse v6.5.0-rc.1'", want: "v6.5.0-rc.1"},
{name: "legacy", legacy: "echo 'Pulse v6.5.0'", want: "v6.5.0"},
{name: "failed", body: "echo 'Pulse v6.5.0'; exit 1"},
{name: "empty", body: ":"},
{name: "pro", body: "echo 'Pulse Pro v6.5.0'"},
{name: "other_brand", body: "echo 'Other v6.5.0 private-detail'"},
{name: "later_line", body: "printf 'Other\\nPulse v6.5.0\\n'"},
{name: "invalid_suffix", body: "echo 'Pulse v6.5.0-extra!'"},
{name: "non_executable", body: "echo 'Pulse v6.5.0'", nonExecutable: true},
{name: "missing"},
{name: "bad_primary_over_legacy", body: "exit 1", legacy: "echo 'Pulse v6.5.0'"},
{name: "primary_directory", directory: true, legacy: "echo 'Pulse v6.5.0'"},
} {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, "bin"), 0700); err != nil {
t.Fatal(err)
}
primary := filepath.Join(dir, "bin", "pulse")
if tc.directory {
if err := os.Mkdir(primary, 0700); err != nil {
t.Fatal(err)
}
} else if tc.body != "" {
mode := os.FileMode(0700)
if tc.nonExecutable {
mode = 0600
}
if err := os.WriteFile(primary, []byte("#!/bin/bash\n"+tc.body+"\n"), mode); err != nil {
t.Fatal(err)
}
}
if tc.legacy != "" {
if err := os.WriteFile(filepath.Join(dir, "pulse"), []byte("#!/bin/bash\n"+tc.legacy+"\n"), 0700); err != nil {
t.Fatal(err)
}
}
if err := os.WriteFile(filepath.Join(dir, "VERSION"), []byte("v6.5.0\n"), 0600); err != nil {
t.Fatal(err)
}
cmd := exec.Command("bash", "-c", "set -euo pipefail\n"+extractAutoUpdateAdmissionFunction(t, "get_current_version")+"\nget_current_version\n")
cmd.Env = append(os.Environ(), "INSTALL_DIR="+dir)
out, err := cmd.CombinedOutput()
want := tc.want
if want == "" {
want = "unknown"
}
if err != nil || strings.TrimSpace(string(out)) != want {
t.Fatalf("version: %v, output %q; want %s", err, out, want)
}
})
}
}
func TestAutoUpdateVersionProbeHasHardDeadline(t *testing.T) {
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, "bin"), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "bin", "pulse"), []byte("#!/bin/bash\ntrap '' TERM\necho 'Pulse v6.5.0'\nexec sleep 60\n"), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "VERSION"), []byte("v6.5.0\n"), 0600); err != nil {
t.Fatal(err)
}
cmd := exec.Command("bash", "-c", extractAutoUpdateAdmissionFunction(t, "get_current_version")+"\nget_current_version\n")
cmd.Env = append(os.Environ(), "INSTALL_DIR="+dir)
start := time.Now()
out, err := cmd.CombinedOutput()
if err != nil || !strings.HasSuffix(string(out), "unknown\n") || time.Since(start) > 9*time.Second {
t.Fatalf("deadline: %v, duration %s, output %s", err, time.Since(start), out)
}
}
@@ -639,6 +639,16 @@ func TestAutoUpdateBackupTransaction(t *testing.T) {
{name: "invalid_signature", fault: "invalid_signature", preflight: true},
{name: "installer_failure", outcome: "failure"},
{name: "wrong_version", outcome: "mismatch"},
{name: "failed_version_probe", outcome: "probe_failure"},
{name: "empty_version_probe", outcome: "probe_empty"},
{name: "unrecognised_version_brand", outcome: "probe_unknown"},
{name: "different_edition", outcome: "probe_pro"},
{name: "nonexecutable_replacement", outcome: "probe_nonexec"},
{name: "missing_replacement", outcome: "probe_missing"},
{name: "version_on_later_line", outcome: "probe_later_line"},
{name: "invalid_version_suffix", outcome: "probe_trailing"},
{name: "valid_build_details", outcome: "probe_details", success: true},
{name: "valid_unprefixed_version", outcome: "probe_unprefixed", success: true},
{name: "original_version_absent", outcome: "failure", noVersion: true},
{name: "extended_metadata", fault: "xattrs", outcome: "failure"},
{name: "legacy_layout_migration", outcome: "failure", layout: "legacy"},
@@ -745,6 +755,20 @@ chmod 700 "$PULSE_INSTALL_DIR/bin/pulse"
if [[ -f "$PULSE_INSTALL_DIR/pulse" ]]; then printf 'changed legacy binary\n' > "$PULSE_INSTALL_DIR/pulse"; fi
printf '%s\n' "$new_version" > "$PULSE_INSTALL_DIR/VERSION"
chmod 600 "$PULSE_INSTALL_DIR/VERSION"
# Keep the target VERSION sidecar even when the signed installer leaves an
# unusable executable. The real post-install probe must trigger full rollback.
case "$OUTCOME" in
probe_failure) printf '#!/bin/bash\necho "Pulse v6.5.0"; exit 1\n' > "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_empty) printf '#!/bin/bash\nexit 0\n' > "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_unknown) printf '#!/bin/bash\necho "Other v6.5.0 private-detail"\n' > "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_pro) printf '#!/bin/bash\necho "Pulse Pro v6.5.0"\n' > "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_nonexec) chmod 600 "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_missing) rm "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_later_line) printf '#!/bin/bash\nprintf "Other\\nPulse v6.5.0\\n"\n' > "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_trailing) printf '#!/bin/bash\necho "Pulse v6.5.0-other!"\n' > "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_details) printf '#!/bin/bash\nprintf "Pulse v6.5.0\\nBuilt: yesterday\\n"\n' > "$PULSE_INSTALL_DIR/bin/pulse" ;;
probe_unprefixed) printf '#!/bin/bash\necho "Pulse 6.5.0"\n' > "$PULSE_INSTALL_DIR/bin/pulse" ;;
esac
if [[ "$OUTCOME" == symlink ]]; then
rm "$PULSE_INSTALL_DIR/bin/pulse"
ln -s "$FIXTURE_DIR/victim" "$PULSE_INSTALL_DIR/bin/pulse"
+19 -15
View File
@@ -115,23 +115,27 @@ check_auto_updates_enabled() {
fi
}
# Get current version
# Read version identity from the executable, never installation metadata. This
# runs both before discovery and after installer success: a VERSION sidecar can
# survive a failed replacement and cannot prove the new binary is usable.
get_current_version() {
local version=""
# Try to get version from binary
if [[ -f "$INSTALL_DIR/bin/pulse" ]]; then
version=$("$INSTALL_DIR/bin/pulse" --version 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.]+)?' | head -1 || true)
elif [[ -f "$INSTALL_DIR/pulse" ]]; then
version=$("$INSTALL_DIR/pulse" --version 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.]+)?' | head -1 || true)
local binary="" version_output="" first_line=""
if [[ -e "$INSTALL_DIR/bin/pulse" || -L "$INSTALL_DIR/bin/pulse" ]]; then
binary="$INSTALL_DIR/bin/pulse"
elif [[ -e "$INSTALL_DIR/pulse" || -L "$INSTALL_DIR/pulse" ]]; then
binary="$INSTALL_DIR/pulse"
fi
# Fallback to VERSION file
if [[ -z "$version" ]] && [[ -f "$INSTALL_DIR/VERSION" ]]; then
version=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null | tr -d '\n' || true)
if [[ -z "$binary" || ! -f "$binary" || ! -x "$binary" ]] ||
! version_output=$(timeout --kill-after=1 5 "$binary" --version 2>/dev/null); then
echo unknown
return 0
fi
first_line=${version_output%%$'\n'*}
if [[ "$first_line" =~ ^Pulse\ v?([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?)$ ]]; then
printf 'v%s\n' "${BASH_REMATCH[1]}"
else
echo unknown
fi
echo "${version:-unknown}"
}
# The unattended updater installs public community builds. The separately
@@ -492,7 +496,7 @@ perform_update() {
# Verify new version
local installed_version
installed_version=$(get_current_version)
if [[ "$installed_version" == "$new_version" ]]; then
if [[ "$installed_version" != "unknown" && "${installed_version#v}" == "${new_version#v}" ]]; then
log info "Version verified: $installed_version"
# If Pulse was running before the update, make sure it is running