cmux NEXT: acpmux trust verdict cache and rollback signature reads off the main thread (needs approval; depends on #22955)

Closed
LA
6d ago
+324-18

Summary

Depends on #22955 (split out of this PR: the acpmux send timeout and the dlopen / SecStaticCodeCheckValidity lint rules). Until #22955 lands, this diff also shows its commit. This PR's own commit is the last one. It removes #22955's two KNOWN pending lint entries, because this PR moves those two calls off main.

Risk tier: high (acpmux trust and the updater). It waits for an approving review from a collaborator other than the author.

SignalCauseFix
Sentry CMUXTERM-MACOS-46JY "App Hanging for at least 8000 ms" (NIGHTLY 3952613909201), 46JZ / 46PA / 7803426091 MainThreadStall 2 s+AcpmuxEnvironment.resolve runs a strict, all-architectures SecStaticCodeCheckValidity of the 72 MB acpmux binary on the main thread, from 8 call sites (QuitAgents.environment, AppServices.modelRouter / chatsFeed, FreeTierAttestStarter, Chief, crash reporter, computer use). Several cold checks ran one after another at launch.A verdict cache, prewarmed off main (below).
Roll Back to Previous Version (static audit)A nested-code validation of the whole running .app, and the clone and prune of kept builds, all on mainThe team is read in rollbackInputs' detached task, the swap runs detached, a second rollback or an install during a swap is refused (RollbackRefusal.inProgress, localized), and the prune lists kept builds without signatures.

How the cached verdict is keyed and invalidated

AcpmuxServerPeer.executableSignedByAppTeamCached(_:team:):

  1. Key. TrustKey holds the symlink-resolved path, the required team ID, and from stat(2) on that path: st_dev, st_ino, st_size, st_mtimespec (sec, nsec) and st_ctimespec (sec, nsec).
  2. Lookup. stat runs again on every call, and the verdict is used only on an exact key match.
    • A swapped binary always gets a new key. A new file at the path (rename, mv, reinstall, app update) has a new inode. An in-place write changes mtime and ctime. A chmod, touch -m or utimes changes ctime, and user space cannot set ctime.
    • So a stale verdict cannot answer for a changed file. A failed stat returns false (untrusted).
  3. Check. On a miss, the same strict check as before runs: requirement anchor apple generic and certificate leaf[subject.OU] = "<team>", flags kSecCSCheckAllArchitectures | kSecCSStrictValidate.
  4. Store. After the check, the key is computed again. The verdict is stored, and returned true, only if the key is unchanged. A file that changed during the check (for example an mmap write) gets false and no cache entry.
  5. One check per key. A second caller for the same key waits for the running check, with no second hash. The wait is bounded by that one check.
  6. Scope. The cache only answers resolve's filter. AcpmuxDaemonLauncher.launch still runs the uncached strict check right before it spawns acpmux. The peer checks on the socket (teamSigned, audit token, LOCAL_PEERPID path) do not change. Verdicts live in process memory only and end with the process.
  7. Prewarm. AgentPaneView.startWarmup(), called from main before the run loop, starts resolve(build: nil) in a detached task. The build does not change which executable resolve picks, so this fills the same key that the main-thread callers look up.

Residual: if the acpmux file changes while the app runs, the next main-thread resolve does one strict check on main again (this is stated in the allow comment).

Changelog

Fixed: cmux NEXT no longer freezes for seconds at launch while it verifies its bundled agent daemon, and Roll Back no longer freezes the window.

Observed / Not run

  • Observed: the content of this PR's commit is the same as the earlier head that passed cmux-ci steps 3376fe7661a1 and adbd391e3c25 (AcpmuxLaunchSecurityTests, AcpmuxHomeIsolationTests, AcpmuxExecutableOverrideTests, AcpmuxSocketPathParityTests, AcpmuxBundleReplacedTests, AcpmuxRefusedDaemonTests, RollbackSwapTests). check-concurrency ok; prepush-gate no new violations. Review findings and their resolution are in the comments.
  • Not run: tagged-app launch timing; a real rollback in a signed build. UNVERIFIED.