CVE-2025-32324 AI-generated analysis
platform/frameworks/base, ActivityManagerService shell command (services/core/java/com/android/server/am/ActivityManagerShellCommand.java)
Moves the runStartActivity(pw) call of the `am start-in-vsync` shell command out of the Choreographer frame callback (posted via the system_server UI handler) back onto the shell command's own thread, so the activity is started with the shell caller's pid/uid.
Root cause
In ActivityManagerShellCommand.onCommand, the "start-in-vsync" case used mInternal.mUiHandler.runWithScissors to post a lambda that registered a Choreographer frame callback, and runStartActivity(pw) was called inside that frame callback. The callback runs on the UI handler thread rather than the thread handling the shell command, so the activity start ran with an incorrect pid/uid (commit title: "Fix starting the activity with incorrect pid/uid"). The result was passed back through a startResult[] array and the ProgressWaiter.
How the fix works
- services/core/java/com/android/server/am/ActivityManagerShellCommand.java:onCommand, case "start-in-vsync": the frame callback body is reduced to waiter.onFinished(0, null). The runStartActivity(pw) call, the startResult[] array and the try/catch that printed "Error: unable to start activity" are removed from the callback.
- After waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS) returns, onCommand now calls and returns runStartActivity(pw) directly on the shell command thread, as the plain "start"/"start-activity" case does.
- The vsync synchronisation remains only as a wait: the command blocks until the next frame callback fires (or the timeout elapses) and then starts the activity.
Completeness
- The fix covers the only place in the diff where runStartActivity was executed off the calling thread (the start-in-vsync frame callback).
- Other onCommand cases shown in the context (start, start-activity, startservice, broadcast, etc.) already call their run* methods directly on the command thread and are not changed.
- The frame callback and the runWithScissors posting to mUiHandler remain. The fix changes only what runs inside the callback.
Behavior changes
- start-in-vsync now starts the activity on the shell command thread after the vsync wait, so the start is attributed to the shell caller's pid/uid.
- The in-callback try/catch is gone. Exceptions from runStartActivity are now handled the same way as for the plain "start" command. Previously such an exception was printed as "Error: unable to start activity, ...", waiter.onFinished was not called, and the command waited out the timeout and returned -1.
- If the frame callback does not fire before the timeout, runStartActivity now still runs after the wait ends. Previously the command returned -1, while the posted callback could still start the activity later.
- The activity start now happens after the frame callback rather than within it.
Per major
- Android 14: Not affected per the bulletin. Only 1/8 lines of the patched region are present at android-security-14.0.0_r29, which suggests the start-in-vsync code is likely absent there.
- Android 15: Same fix: commit 0fb2788dac39 in android-security-15.0.0_r10 (cherry-picked from fca9c788b336 / b04474764693). The fixed code is shown at android-security-15.0.0_r17 (5/8 patched-region lines present).
- Android 16: Same fix: commit 55361a549fc2 in android-security-16.0.0_r1 with an identical diff (different line offset, @@ -250). 5/8 patched-region lines present at android-security-16.0.0_r8.
- Android 17: Not affected per the bulletin (Android 17 is not listed). 5/8 lines of the patched region are present at android-security-17.0.0_r1.
Unknowns
- The material does not show which identity checks in the activity start path were affected by the wrong pid/uid, nor the exact impact behind the High EoP rating.
- The material does not show the commit that introduced start-in-vsync or in which release it first appeared.
- The material does not directly confirm that the start-in-vsync code is absent on Android 14. Only the 1/8 line match is given.