Hotfix/client update reinstall hardening - #2301
Merged
danylo-babenko-flamingo merged 6 commits intoSep 23, 2026
Merged
Conversation
danylo-babenko-flamingo
marked this pull request as ready for review
September 23, 2026 10:50
Contributor
🦩 Flamingo Code Review2 finding(s) — 0 action required · 2 recommended · 0 informational Mode: advisory · 2 defect(s) outside any rule Inline comments: 2 new Need another pass? Commits pushed after this review are not reviewed automatically.
Prefer typing? Comment React 👍/👎 on inline comments to teach the reviewer. Started 2026-09-23 10:50 UTC · updated 2026-09-23 10:51 UTC · workflow run |
danylo-babenko-flamingo
force-pushed
the
hotfix/client-update-reinstall-hardening
branch
from
September 23, 2026 12:39
5e4d466 to
5739b39
Compare
denys-gif
reviewed
Sep 23, 2026
denys-gif
reviewed
Sep 23, 2026
denys-gif
reviewed
Sep 23, 2026
denys-gif
reviewed
Sep 23, 2026
denys-gif
reviewed
Sep 23, 2026
denys-gif
reviewed
Sep 23, 2026
denys-gif
reviewed
Sep 23, 2026
denys-gif
reviewed
Sep 23, 2026
Contributor
|
please remove extensive comments or shorten them |
…ading, reuse the canonical path resolver, trim comments
…ich owns that file
…reinstall-hardening
danylo-babenko-flamingo
enabled auto-merge (squash)
September 23, 2026 17:40
denys-gif
approved these changes
Sep 23, 2026
danylo-babenko-flamingo
deleted the
hotfix/client-update-reinstall-hardening
branch
September 23, 2026 19:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Harden tool supervision, updates and reinstall against partial failures
Problem
A Windows endpoint went dark for 12 days on 7 Sep. The update to 1.3.10 succeeded; 9 seconds later the agent exited.
One leftover
fleetmdm-agentwas wedged in the kernel and survived three force-kills. Before launching a tool we clear its leftovers, and that error propagated through five?frames —tool_kill_service→run_tool→run()→Client::start()→ service core. One stuck tool killed the whole agent.Nobody noticed because the service reported
SERVICE_STOPPEDwithWin32(0)— identical to a clean stop — so SCM never fired the 10s/60s/300s recovery ladder the agent configures for itself. The failure reason went toeprintln!and never reached the log.An audit found the same shape elsewhere: operations that fail partway leave the agent, a tool or its enrollment worse off than before. Those are fixed here too.
Fix
Supervision — the kill moved inside the supervisor loop, where a stuck leftover is now a retryable launch failure (delay escalates 5s → 300s) instead of a fatal error. It never launches beside a live leftover, and self-heals when the process clears.
run()also isolates per tool, andlib.rsno longer lets the tool lane end the service core.Windows recovery —
service.rsreportsServiceSpecific(1)on failure andWin32(0)only on a graceful stop, so the configured recovery ladder can finally fire. Reason now logged viatracing::error!.Updates —
tool_agent_update_servicetakestool_lockas its first action, before the record is read, like install/uninstall/restart already do, and uses theDrop-basedUpdatingGuardso a leaked flag can't park the supervisor. Asset-only updates now restart the tool (nothing did before:run_toolreturns early forInstallation::Serviceandrun()never starts services, so a routine osqueryd bump stopped orbit for good) —Serviceviastart_service, macOSGuiAppvia a fresh supervisor. The stop usesstop_installed_toolrather than a process kill — on macOS a pattern kill leaves the launchd job loaded, making the follow-uplaunchctl loada silent no-op.Don't destroy before replacing — the macOS
.appis moved aside inprepareand restored by a realrollback, and a backup left behind by an interrupted update is adopted rather than clobbered; the Standard updater honours the recordedexecutable_path; andseed_if_missingno longer seeds the last-known-good reserve from a binary that disagrees with the anchor (prerequisite for #1783, whose rollback trusts that anchor).Durability & security —
initial_config.jsonusesatomic_write; macOS preferences go throughlaunchctl asuser(from a LaunchDaemon there's no session bootstrap, sodefaults writefailed and the GUI app launched with noserverUrl); archive extraction rejects absolute and..entry paths, which could otherwise write anywhere as root.Verification
Both Rust legs are green on
051613f11.Test Rust (windows-latest)is the one that matters —service.rsis#[cfg(windows)]and cannot be built on macOS (cross-compiling needs an MSVC toolchain forzstd-sys), so that job is the only real compile of the exit-code change; a red or skipped Windows check means it is unverified.Scan Codefails repo-wide (same on #2341) and is unrelated to this branch.Compatible with the other open client PRs — merge order doesn't matter. Merged locally with #2230, #2231 and #2287: no conflicts, clippy clean, tests pass, and every side's logic survives byte-identically. Only #2287 shares a file (
tool_agent_update_service.rs), and the resulting order is the right one — its downgrade guard refuses a stale message before this PR takes the tool lock.Locally: lint clean, 195 tests (was 187) — new coverage for
safe_join.Still needs a real endpoint: wedge an
agent.exeso it survivesTerminateProcessand confirm the agent stays up; kill the service core and confirm SCM restarts it.Risk & follow-ups
run_toolis infallible by design, so an install can report success for a tool that never launches; a faileddefaults writeblocks the GuiApp launch entirely; install and uninstall still pair the updating flag by hand instead of usingUpdatingGuard.ServiceToolUpdater(fix(client): flush the written binary and back off the Windows service start (os error 32) #2230) and sweeping stale.update-backupbundles on uninstall (fix(client): recover from a locked tool directory on reinstall #2231).record_boot_attemptskips whentarget == running); force reinstall silently mints a new machine identity, a likely source of the duplicate-host and 401 enrollment loops on this tenant.