cmux NEXT: acpmux trust verdict cache and rollback signature reads off the main thread (needs approval; depends on #22955)
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.
| Signal | Cause | Fix |
|---|---|---|
| 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 main | The 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:):
- Key.
TrustKeyholds the symlink-resolved path, the required team ID, and fromstat(2)on that path:st_dev,st_ino,st_size,st_mtimespec(sec, nsec) andst_ctimespec(sec, nsec). - Lookup.
statruns 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. Achmod,touch -morutimeschanges ctime, and user space cannot set ctime. - So a stale verdict cannot answer for a changed file. A failed
statreturns false (untrusted).
- A swapped binary always gets a new key. A new file at the path (rename,
- Check. On a miss, the same strict check as before runs: requirement
anchor apple generic and certificate leaf[subject.OU] = "<team>", flagskSecCSCheckAllArchitectures | kSecCSStrictValidate. - 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.
- 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.
- Scope. The cache only answers
resolve's filter.AcpmuxDaemonLauncher.launchstill runs the uncached strict check right before it spawns acpmux. The peer checks on the socket (teamSigned, audit token,LOCAL_PEERPIDpath) do not change. Verdicts live in process memory only and end with the process. - Prewarm.
AgentPaneView.startWarmup(), called frommainbefore the run loop, startsresolve(build: nil)in a detached task. The build does not change which executableresolvepicks, 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.